28 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
21 changed files with 1691 additions and 663 deletions

View File

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

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
- ...
![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
- tables
@@ -22,7 +22,7 @@ There is 3 main schemas:
- ...
- 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
- tables
@@ -31,5 +31,5 @@ There is 3 main schemas:
- ...
- 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

@@ -79,5 +79,42 @@ services:
# retries: 5
# start_period: 100s
telegram:
image: xbgmsharp/postgsail-telegram-bot
container_name: telegram
restart: unless-stopped
volumes:
- /etc/resolv.conf:/etc/resolv.conf:ro
ports:
- "3005:8080"
network_mode: "host"
env_file: .env
environment:
- BOT_TOKEN=${PGSAIL_TELEGRAM_BOT_TOKEN}
- PGSAIL_URL=${PGSAIL_API_URL}
depends_on:
- db
- api
logging:
options:
max-size: 10m
web:
image: xbgmsharp/postgsail-vuestic
container_name: web
restart: unless-stopped
volumes:
- /etc/resolv.conf:/etc/resolv.conf:ro
ports:
- "3006:8080"
network_mode: "host"
env_file: .env
depends_on:
- db
- api
logging:
options:
max-size: 10m
volumes:
data: {}

View File

@@ -82,18 +82,19 @@ UPDATE pg_language SET lanpltrusted = true WHERE lanname = 'plpython3u';
-- Metadata from signalk
CREATE TABLE IF NOT EXISTS api.metadata(
id SERIAL PRIMARY KEY,
name VARCHAR(150) NULL,
name TEXT NULL,
mmsi NUMERIC NULL,
client_id VARCHAR(255) UNIQUE NOT NULL,
client_id TEXT NULL,
-- vessel_id link auth.vessels with api.metadata
vessel_id TEXT NOT NULL UNIQUE,
length DOUBLE PRECISION NULL,
beam DOUBLE PRECISION NULL,
height DOUBLE PRECISION NULL,
ship_type NUMERIC NULL,
plugin_version VARCHAR(10) NOT NULL,
signalk_version VARCHAR(10) NOT NULL,
plugin_version TEXT NOT NULL,
signalk_version TEXT NOT NULL,
time TIMESTAMP WITHOUT TIME ZONE NOT NULL, -- should be rename to last_update !?
active BOOLEAN DEFAULT True, -- trigger monitor online/offline
-- vessel_id link auth.vessels with api.metadata
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);
@@ -103,8 +104,8 @@ COMMENT ON TABLE
IS 'Stores metadata from vessel';
COMMENT ON COLUMN api.metadata.active IS 'trigger monitor online/offline';
-- Index
CREATE INDEX metadata_client_id_idx ON api.metadata (client_id);
CREATE INDEX metadata_mmsi_idx ON api.metadata (mmsi);
CREATE INDEX metadata_vessel_id_idx ON api.metadata (vessel_id);
--CREATE INDEX metadata_mmsi_idx ON api.metadata (mmsi);
CREATE INDEX metadata_name_idx ON api.metadata (name);
---------------------------------------------------------------------------
@@ -114,7 +115,9 @@ CREATE TYPE status AS ENUM ('sailing', 'motoring', 'moored', 'anchored');
-- Table api.metrics
CREATE TABLE IF NOT EXISTS api.metrics (
time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
client_id TEXT NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
latitude DOUBLE PRECISION NULL,
longitude DOUBLE PRECISION NULL,
speedOverGround DOUBLE PRECISION NULL,
@@ -123,7 +126,7 @@ CREATE TABLE IF NOT EXISTS api.metrics (
angleSpeedApparent DOUBLE PRECISION NULL,
status status NULL,
metrics jsonb NULL,
CONSTRAINT valid_client_id CHECK (length(client_id) > 10),
--CONSTRAINT valid_client_id CHECK (length(client_id) > 10),
CONSTRAINT valid_latitude CHECK (latitude >= -90 and latitude <= 90),
CONSTRAINT valid_longitude CHECK (longitude >= -180 and longitude <= 180)
);
@@ -135,21 +138,22 @@ COMMENT ON COLUMN api.metrics.latitude IS 'With CONSTRAINT but allow NULL value
COMMENT ON COLUMN api.metrics.longitude IS 'With CONSTRAINT but allow NULL value to be ignored silently by trigger';
-- Index
CREATE INDEX ON api.metrics (client_id, time DESC);
CREATE INDEX ON api.metrics (vessel_id, time DESC);
CREATE INDEX ON api.metrics (status, time DESC);
-- json index??
CREATE INDEX ON api.metrics using GIN (metrics);
-- timescaledb hypertable
--SELECT create_hypertable('api.metrics', 'time');
SELECT create_hypertable('api.metrics', 'time', chunk_time_interval => INTERVAL '7 day');
-- timescaledb hypertable with space partitions
SELECT create_hypertable('api.metrics', 'time', 'client_id',
number_partitions => 2,
chunk_time_interval => INTERVAL '7 day',
if_not_exists => true);
-- ERROR: new row for relation "_hyper_1_2_chunk" violates check constraint "constraint_4"
-- ((_timescaledb_internal.get_partition_hash(vessel_id) < 1073741823))
--SELECT create_hypertable('api.metrics', 'time', 'vessel_id',
-- number_partitions => 2,
-- chunk_time_interval => INTERVAL '7 day',
-- if_not_exists => true);
---------------------------------------------------------------------------
-- Logbook
-- todo add clientid ref
-- todo add cosumption fuel?
-- todo add engine hour?
-- todo add geom object http://epsg.io/4326 EPSG:4326 Unit: degres
@@ -162,8 +166,9 @@ SELECT create_hypertable('api.metrics', 'time', 'client_id',
-- https://www.reddit.com/r/PostgreSQL/comments/di5mbr/postgresql_12_foreign_keys_and_partitioned_tables/f3tsoop/
CREATE TABLE IF NOT EXISTS api.logbook(
id SERIAL PRIMARY KEY,
client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
-- client_id VARCHAR(255) NOT NULL,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
active BOOLEAN DEFAULT false,
name VARCHAR(255),
_from VARCHAR(255),
@@ -176,7 +181,7 @@ CREATE TABLE IF NOT EXISTS api.logbook(
track_geom geometry(LINESTRING,4326) NULL,
track_geog geography(LINESTRING) NULL,
track_geojson JSON NULL,
-- track_gpx XML NULL,
track_gpx XML NULL,
_from_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
_to_time TIMESTAMP WITHOUT TIME ZONE NULL,
distance NUMERIC, -- meters?
@@ -193,24 +198,23 @@ COMMENT ON TABLE
COMMENT ON COLUMN api.logbook.distance IS 'in NM';
-- Index todo!
CREATE INDEX logbook_client_id_idx ON api.logbook (client_id);
CREATE INDEX logbook_vessel_id_idx ON api.logbook (vessel_id);
CREATE INDEX ON api.logbook USING GIST ( track_geom );
COMMENT ON COLUMN api.logbook.track_geom IS 'postgis geometry type EPSG:4326 Unit: degres';
CREATE INDEX ON api.logbook USING GIST ( track_geog );
COMMENT ON COLUMN api.logbook.track_geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- Otherwise -- ERROR: Only lon/lat coordinate systems are supported in geography.
COMMENT ON COLUMN api.logbook.track_geojson IS 'store the geojson track metrics data, can not depend api.metrics table, should be generate from linetring to save disk space?';
--COMMENT ON COLUMN api.logbook.track_gpx IS 'store the gpx track metrics data, can not depend api.metrics table, should be generate from linetring to save disk space?';
COMMENT ON COLUMN api.logbook.track_gpx IS 'store the gpx track metrics data, can not depend api.metrics table, should be generate from linetring to save disk space?';
---------------------------------------------------------------------------
-- Stays
-- todo add clientid ref
-- todo add FOREIGN KEY?
-- virtual logbook by boat?
CREATE TABLE IF NOT EXISTS api.stays(
id SERIAL PRIMARY KEY,
client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
-- client_id VARCHAR(255) NOT NULL,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
active BOOLEAN DEFAULT false,
name VARCHAR(255),
latitude DOUBLE PRECISION NULL,
@@ -228,21 +232,21 @@ COMMENT ON TABLE
IS 'Stores generated stays';
-- Index
CREATE INDEX stays_client_id_idx ON api.stays (client_id);
CREATE INDEX stays_vessel_id_idx ON api.stays (vessel_id);
CREATE INDEX ON api.stays USING GIST ( geog );
COMMENT ON COLUMN api.stays.geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- With other SRID ERROR: Only lon/lat coordinate systems are supported in geography.
---------------------------------------------------------------------------
-- Moorages
-- todo add clientid ref
-- virtual logbook by boat?
CREATE TABLE IF NOT EXISTS api.moorages(
id SERIAL PRIMARY KEY,
client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
-- client_id VARCHAR(255) NOT NULL,
name VARCHAR(255),
country VARCHAR(255), -- todo need to update reverse_geocode_py_fn
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
name TEXT,
country TEXT, -- todo need to update reverse_geocode_py_fn
stay_id INT NOT NULL, -- needed?
stay_code INT DEFAULT 1, -- needed? REFERENCES api.stays_at(stay_code)
stay_duration INTERVAL NULL,
@@ -259,7 +263,7 @@ COMMENT ON TABLE
IS 'Stores generated moorages';
-- Index
CREATE INDEX moorages_client_id_idx ON api.moorages (client_id);
CREATE INDEX moorages_vessel_id_idx ON api.moorages (vessel_id);
CREATE INDEX ON api.moorages USING GIST ( geog );
COMMENT ON COLUMN api.moorages.geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- With other SRID ERROR: Only lon/lat coordinate systems are supported in geography.
@@ -290,20 +294,21 @@ CREATE FUNCTION metadata_upsert_trigger_fn() RETURNS trigger AS $metadata_upsert
metadata_active boolean;
BEGIN
-- Set client_id to new value to allow RLS
PERFORM set_config('vessel.client_id', NEW.client_id, false);
--PERFORM set_config('vessel.client_id', NEW.client_id, false);
-- UPSERT - Insert vs Update for Metadata
RAISE NOTICE 'metadata_upsert_trigger_fn';
--PERFORM set_config('vessel.id', NEW.vessel_id, true);
RAISE WARNING 'metadata_upsert_trigger_fn [%] [%]', current_setting('vessel.id', true), NEW;
SELECT m.id,m.active INTO metadata_id, metadata_active
FROM api.metadata m
WHERE (m.vessel_id IS NOT NULL AND m.vessel_id = current_setting('vessel.id', true))
OR (m.client_id IS NOT NULL AND m.client_id = NEW.client_id);
WHERE m.vessel_id IS NOT NULL AND m.vessel_id = current_setting('vessel.id', true);
RAISE NOTICE 'metadata_id %', metadata_id;
IF metadata_id IS NOT NULL THEN
-- send notifitacion if boat is back online
IF metadata_active is False THEN
-- Add monitor online entry to process queue for later notification
INSERT INTO process_queue (channel, payload, stored)
VALUES ('monitoring_online', metadata_id, now());
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('monitoring_online', metadata_id, now(), current_setting('vessel.id', true));
END IF;
-- Update vessel metadata
UPDATE api.metadata
@@ -350,9 +355,9 @@ DROP FUNCTION IF EXISTS metadata_notification_trigger_fn;
CREATE FUNCTION metadata_notification_trigger_fn() RETURNS trigger AS $metadata_notification$
DECLARE
BEGIN
RAISE NOTICE 'metadata_notification_trigger_fn';
INSERT INTO process_queue (channel, payload, stored)
VALUES ('monitoring_online', NEW.id, now());
RAISE NOTICE 'metadata_notification_trigger_fn [%]', NEW;
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('monitoring_online', NEW.id, now(), NEW.vessel_id);
RETURN NULL;
END;
$metadata_notification$ LANGUAGE plpgsql;
@@ -395,55 +400,57 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
logbook_id integer;
stay_id integer;
valid_status BOOLEAN;
_vessel_id TEXT;
BEGIN
-- Set client_id to new value to allow RLS
PERFORM set_config('vessel.client_id', NEW.client_id, false);
--PERFORM set_config('vessel.client_id', NEW.client_id, false);
NEW.vessel_id = current_setting('vessel.id', true);
--RAISE NOTICE 'metrics_trigger_fn client_id [%]', NEW.client_id;
-- Boat metadata are check using api.metrics REFERENCES to api.metadata
-- Fetch the latest entry to compare status against the new status to be insert
SELECT coalesce(m.status, 'moored'), m.time INTO previous_status, previous_time
FROM api.metrics m
WHERE m.client_id IS NOT NULL
AND m.client_id = NEW.client_id
WHERE m.vessel_id IS NOT NULL
AND m.vessel_id = current_setting('vessel.id', true)
ORDER BY m.time DESC LIMIT 1;
--RAISE NOTICE 'Metrics Status, New:[%] Previous:[%]', NEW.status, previous_status;
IF previous_time = NEW.time THEN
-- Ignore entry if same time
RAISE WARNING 'Metrics Ignoring metric, duplicate time [%] = [%]', previous_time, NEW.time;
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], duplicate time [%] = [%]', NEW.vessel_id, previous_time, NEW.time;
RETURN NULL;
END IF;
IF previous_time > NEW.time THEN
-- Ignore entry if new time is later than previous time
RAISE WARNING 'Metrics Ignoring metric, new time is older [%] > [%]', previous_time, NEW.time;
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], new time is older [%] > [%]', NEW.vessel_id, previous_time, NEW.time;
RETURN NULL;
END IF;
-- Check if latitude or longitude are null
IF NEW.latitude IS NULL OR NEW.longitude IS NULL THEN
-- Ignore entry if null latitude,longitude
RAISE WARNING 'Metrics Ignoring metric, null latitude,longitude [%] [%]', NEW.latitude, NEW.longitude;
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], null latitude,longitude [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
RETURN NULL;
END IF;
-- Check if status is null
IF NEW.status IS NULL THEN
RAISE WARNING 'Metrics Unknow NEW.status from vessel [%], set to default moored', NEW.status;
RAISE WARNING 'Metrics Unknow NEW.status, vessel_id [%], null status, set to default moored from [%]', NEW.vessel_id, NEW.status;
NEW.status := 'moored';
END IF;
IF previous_status IS NULL THEN
IF NEW.status = 'anchored' THEN
RAISE WARNING 'Metrics Unknow previous_status from vessel [%], set to default current status [%]', previous_status, NEW.status;
RAISE WARNING 'Metrics Unknow previous_status from vessel_id [%], [%] set to default current status [%]', NEW.vessel_id, previous_status, NEW.status;
previous_status := NEW.status;
ELSE
RAISE WARNING 'Metrics Unknow previous_status from vessel [%], set to default status moored vs [%]', previous_status, NEW.status;
RAISE WARNING 'Metrics Unknow previous_status from vessel_id [%], [%] set to default status moored vs [%]', NEW.vessel_id, previous_status, NEW.status;
previous_status := 'moored';
END IF;
-- Add new stay as no previous entry exist
INSERT INTO api.stays
(client_id, active, arrived, latitude, longitude, stay_code)
VALUES (NEW.client_id, true, NEW.time, NEW.latitude, NEW.longitude, 1)
(vessel_id, active, arrived, latitude, longitude, stay_code)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude, 1)
RETURNING id INTO stay_id;
-- Add stay entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored)
VALUES ('new_stay', stay_id, now());
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_stay', stay_id, now(), current_setting('vessel.id', true));
RAISE WARNING 'Metrics Insert first stay as no previous metrics exist, stay_id %', stay_id;
END IF;
-- Check if status is valid enum
@@ -461,11 +468,11 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
OR (NEW.status::TEXT = 'motoring' AND previous_status::TEXT <> 'sailing') ) THEN
RAISE WARNING 'Metrics Update status, try new logbook, New:[%] Previous:[%]', NEW.status, previous_status;
-- Start new log
logbook_id := public.trip_in_progress_fn(NEW.client_id::TEXT);
logbook_id := public.trip_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF logbook_id IS NULL THEN
INSERT INTO api.logbook
(client_id, active, _from_time, _from_lat, _from_lng)
VALUES (NEW.client_id, true, NEW.time, NEW.latitude, NEW.longitude)
(vessel_id, active, _from_time, _from_lat, _from_lng)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude)
RETURNING id INTO logbook_id;
RAISE WARNING 'Metrics Insert new logbook, logbook_id %', logbook_id;
ELSE
@@ -480,7 +487,7 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
END IF;
-- End current stay
stay_id := public.stay_in_progress_fn(NEW.client_id::TEXT);
stay_id := public.stay_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF stay_id IS NOT NULL THEN
UPDATE api.stays
SET
@@ -489,8 +496,8 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
WHERE id = stay_id;
RAISE WARNING 'Metrics Updating Stay end current stay_id [%] [%] [%]', stay_id, NEW.status, NEW.time;
-- Add moorage entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored)
VALUES ('new_moorage', stay_id, now());
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_moorage', stay_id, now(), current_setting('vessel.id', true));
ELSE
RAISE WARNING 'Metrics Invalid stay_id [%] [%]', stay_id, NEW.time;
END IF;
@@ -501,7 +508,7 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
OR (NEW.status::TEXT = 'anchored' AND previous_status::TEXT <> 'moored') ) THEN
-- Start new stays
RAISE WARNING 'Metrics Update status, try new stay, New:[%] Previous:[%]', NEW.status, previous_status;
stay_id := public.stay_in_progress_fn(NEW.client_id::TEXT);
stay_id := public.stay_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF stay_id IS NULL THEN
RAISE WARNING 'Metrics Inserting new stay [%]', NEW.status;
-- If metric status is anchored set stay_code accordingly
@@ -511,12 +518,12 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
END IF;
-- Add new stay
INSERT INTO api.stays
(client_id, active, arrived, latitude, longitude, stay_code)
VALUES (NEW.client_id, true, NEW.time, NEW.latitude, NEW.longitude, stay_code)
(vessel_id, active, arrived, latitude, longitude, stay_code)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude, stay_code)
RETURNING id INTO stay_id;
-- Add stay entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored)
VALUES ('new_stay', stay_id, now());
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_stay', stay_id, now(), current_setting('vessel.id', true));
ELSE
RAISE WARNING 'Metrics Invalid stay_id [%] [%]', stay_id, NEW.time;
UPDATE api.stays
@@ -527,8 +534,8 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
END IF;
-- End current log/trip
-- Fetch logbook_id by client_id
logbook_id := public.trip_in_progress_fn(NEW.client_id::TEXT);
-- Fetch logbook_id by vessel_id
logbook_id := public.trip_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF logbook_id IS NOT NULL THEN
-- todo check on time start vs end
RAISE WARNING 'Metrics Updating logbook status [%] [%] [%]', logbook_id, NEW.status, NEW.time;
@@ -540,8 +547,8 @@ CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
_to_lng = NEW.longitude
WHERE id = logbook_id;
-- Add logbook entry to process queue for later processing
INSERT INTO process_queue (channel, payload, stored)
VALUEs ('new_logbook', logbook_id, now());
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUEs ('new_logbook', logbook_id, now(), current_setting('vessel.id', true));
ELSE
RAISE WARNING 'Metrics Invalid logbook_id [%] [%]', logbook_id, NEW.time;
END IF;
@@ -628,7 +635,7 @@ CREATE FUNCTION api.export_logbook_geojson_fn(IN _id integer, OUT geojson JSON)
SELECT * INTO logbook_rec
FROM api.logbook WHERE id = _id;
-- Ensure the query is successful
IF logbook_rec.client_id IS NULL THEN
IF logbook_rec.vessel_id IS NULL THEN
RAISE WARNING '-> export_logbook_geojson_fn invalid logbook %', _id;
RETURN;
END IF;
@@ -660,7 +667,7 @@ AS $export_logbook_gpx$
api.logbook l
WHERE l.id = _id;
-- Ensure the query is successful
IF log_rec.client_id IS NULL THEN
IF log_rec.vessel_id IS NULL THEN
RAISE WARNING '-> export_logbook_gpx_fn invalid logbook %', _id;
RETURN '';
END IF;
@@ -696,7 +703,7 @@ AS $export_logbook_gpx$
AND m.longitude IS NOT NULL
AND m.time >= log_rec._from_time::TIMESTAMP WITHOUT TIME ZONE
AND m.time <= log_rec._to_time::TIMESTAMP WITHOUT TIME ZONE
AND client_id = log_rec.client_id;
AND vessel_id = log_rec.vessel_id;
-- ERROR: column "m.time" must appear in the GROUP BY clause or be used in an aggregate function at character 2304
--ORDER BY m.time ASC;
END;
@@ -808,14 +815,14 @@ COMMENT ON FUNCTION
-- trip_in_progress_fn
DROP FUNCTION IF EXISTS public.trip_in_progress_fn;
CREATE FUNCTION public.trip_in_progress_fn(IN _client_id TEXT) RETURNS INT AS $trip_in_progress$
CREATE FUNCTION public.trip_in_progress_fn(IN _vessel_id TEXT) RETURNS INT AS $trip_in_progress$
DECLARE
logbook_id INT := NULL;
BEGIN
SELECT id INTO logbook_id
FROM api.logbook l
WHERE l.client_id IS NOT NULL
AND l.client_id = _client_id
WHERE l.vessel_id IS NOT NULL
AND l.vessel_id = _vessel_id
AND active IS true
LIMIT 1;
RETURN logbook_id;
@@ -828,14 +835,14 @@ COMMENT ON FUNCTION
-- stay_in_progress_fn
DROP FUNCTION IF EXISTS public.stay_in_progress_fn;
CREATE FUNCTION public.stay_in_progress_fn(IN _client_id TEXT) RETURNS INT AS $stay_in_progress$
CREATE FUNCTION public.stay_in_progress_fn(IN _vessel_id TEXT) RETURNS INT AS $stay_in_progress$
DECLARE
stay_id INT := NULL;
BEGIN
SELECT id INTO stay_id
FROM api.stays s
WHERE s.client_id IS NOT NULL
AND s.client_id = _client_id
WHERE s.vessel_id IS NOT NULL
AND s.vessel_id = _vessel_id
AND active IS true
LIMIT 1;
RETURN stay_id;
@@ -1035,7 +1042,7 @@ COMMENT ON VIEW
-- Stays web view
-- TODO group by month
DROP VIEW IF EXISTS api.stays_view;
CREATE VIEW api.stays_view WITH (security_invoker=true,security_barrier=true) AS
CREATE OR REPLACE VIEW api.stays_view WITH (security_invoker=true,security_barrier=true) AS
SELECT s.id,
concat(
extract(DAYS FROM (s.departed-s.arrived)::interval),
@@ -1068,7 +1075,7 @@ COMMENT ON VIEW
IS 'Stays web view';
DROP VIEW IF EXISTS api.stay_view;
CREATE VIEW api.stay_view WITH (security_invoker=true,security_barrier=true) AS
CREATE OR REPLACE VIEW api.stay_view WITH (security_invoker=true,security_barrier=true) AS
SELECT s.id,
concat(
extract(DAYS FROM (s.departed-s.arrived)::interval),
@@ -1180,7 +1187,7 @@ COMMENT ON VIEW
----> select sum(l.duration) as "Total Time Underway" from api.logbook l;
-- Longest Nonstop Sail from logbook, eg longest trip duration and distance
----> select max(l.duration),max(l.distance) from api.logbook l;
CREATE VIEW api.stats_logs_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
CREATE OR REPLACE VIEW api.stats_logs_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
WITH
meta AS (
SELECT m.name FROM api.metadata m ),
@@ -1219,7 +1226,7 @@ COMMENT ON VIEW
----> select sum(m.stay_duration) as "Time Spent Away" from api.moorages m where home_flag is false;
-- Time Spent Away order by, group by stay_code (Dock, Anchor, Mooring Buoys, Unclassified)
----> select sa.description,sum(m.stay_duration) as "Time Spent Away" from api.moorages m, api.stays_at sa where home_flag is false AND m.stay_code = sa.stay_code group by m.stay_code,sa.description order by m.stay_code;
CREATE VIEW api.stats_moorages_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
CREATE OR REPLACE VIEW api.stats_moorages_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
WITH
home_ports AS (
select count(*) as home_ports from api.moorages m where home_flag is true
@@ -1243,7 +1250,7 @@ COMMENT ON VIEW
api.stats_moorages_view
IS 'Statistics Moorages web view';
CREATE VIEW api.stats_moorages_away_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
CREATE OR REPLACE VIEW api.stats_moorages_away_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
SELECT sa.description,sum(m.stay_duration) as time_spent_away_by
FROM api.moorages m, api.stays_at sa
WHERE home_flag IS false
@@ -1269,7 +1276,8 @@ COMMENT ON VIEW
-- IS 'Statistics Moorages Time Spent Away web view';
-- View main monitoring for web app
CREATE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=true) AS
DROP VIEW IF EXISTS api.monitoring_view;
CREATE OR REPLACE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=true) AS
SELECT
time AS "time",
(NOW() AT TIME ZONE 'UTC' - time) > INTERVAL '70 MINUTES' as offline,
@@ -1282,6 +1290,8 @@ CREATE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=tru
metrics-> 'environment.outside.humidity' AS outsideHumidity,
metrics-> 'environment.outside.pressure' AS outsidePressure,
metrics-> 'environment.inside.pressure' AS insidePressure,
metrics-> 'electrical.batteries.House.capacity.stateOfCharge' AS batteryCharge,
metrics-> 'electrical.batteries.House.voltage' AS batteryVoltage,
jsonb_build_object(
'type', 'Feature',
'geometry', ST_AsGeoJSON(st_makepoint(longitude,latitude))::jsonb,

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ CREATE SCHEMA IF NOT EXISTS public;
---------------------------------------------------------------------------
-- Functions public schema
-- process single cron event, process_[logbook|stay|moorage|badge]_queue_fn()
-- process single cron event, process_[logbook|stay|moorage]_queue_fn()
--
CREATE OR REPLACE FUNCTION logbook_metrics_dwithin_fn(
@@ -29,7 +29,7 @@ CREATE OR REPLACE FUNCTION logbook_metrics_dwithin_fn(
AND m.longitude IS NOT NULL
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
AND m.time <= _end::TIMESTAMP WITHOUT TIME ZONE
AND client_id = current_setting('vessel.client_id', false)
AND vessel_id = current_setting('vessel.id', false)
AND ST_DWithin(
Geography(ST_MakePoint(m.longitude, m.latitude)),
Geography(ST_MakePoint(lgn, lat)),
@@ -62,7 +62,7 @@ CREATE OR REPLACE FUNCTION logbook_update_avg_fn(
AND m.longitude IS NOT NULL
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
AND m.time <= _end::TIMESTAMP WITHOUT TIME ZONE
AND client_id = current_setting('vessel.client_id', false);
AND vessel_id = current_setting('vessel.id', false);
RAISE NOTICE '-> Updated avg for logbook id=%, avg_speed:%, max_speed:%, max_wind_speed:%, count:%', _id, avg_speed, max_speed, max_wind_speed, count_metric;
END;
$logbook_update_avg$ LANGUAGE plpgsql;
@@ -89,7 +89,7 @@ CREATE FUNCTION logbook_update_geom_distance_fn(IN _id integer, IN _start text,
AND m.longitude IS NOT NULL
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
AND m.time <= _end::TIMESTAMP WITHOUT TIME ZONE
AND client_id = current_setting('vessel.client_id', false)
AND vessel_id = current_setting('vessel.id', false)
ORDER BY m.time ASC
)
) INTO _track_geom;
@@ -150,7 +150,7 @@ CREATE FUNCTION logbook_update_geojson_fn(IN _id integer, IN _start text, IN _en
AND m.longitude IS NOT NULL
AND time >= _start::TIMESTAMP WITHOUT TIME ZONE
AND time <= _end::TIMESTAMP WITHOUT TIME ZONE
AND client_id = current_setting('vessel.client_id', false)
AND vessel_id = current_setting('vessel.id', false)
ORDER BY m.time ASC
)
) AS t;
@@ -207,13 +207,13 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
AND _to_lng IS NOT NULL
AND _to_lat IS NOT NULL;
-- Ensure the query is successful
IF logbook_rec.client_id IS NULL THEN
IF logbook_rec.vessel_id IS NULL THEN
RAISE WARNING '-> process_logbook_queue_fn invalid logbook %', _id;
RETURN;
END IF;
PERFORM set_config('vessel.client_id', logbook_rec.client_id, false);
--RAISE WARNING 'public.process_logbook_queue_fn() scheduler vessel.client_id %', current_setting('vessel.client_id', false);
PERFORM set_config('vessel.id', logbook_rec.vessel_id, false);
--RAISE WARNING 'public.process_logbook_queue_fn() scheduler vessel.id %, user.id', current_setting('vessel.id', false), current_setting('user.id', false);
-- Check if all metrics are within 10meters base on geo loc
count_metric := logbook_metrics_dwithin_fn(logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT, logbook_rec._from_lng::NUMERIC, logbook_rec._from_lat::NUMERIC);
@@ -240,7 +240,7 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
SET status = 'moored'
WHERE time >= logbook_rec._from_time::TIMESTAMP WITHOUT TIME ZONE
AND time <= logbook_rec._to_time::TIMESTAMP WITHOUT TIME ZONE
AND client_id = current_setting('vessel.client_id', false);
AND vessel_id = current_setting('vessel.id', false);
-- Update logbook
UPDATE api.logbook
SET notes = 'invalid logbook data, stationary need to fix metrics?'
@@ -248,17 +248,17 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
-- Get related stays
SELECT id,departed,active INTO current_stays_id,current_stays_departed,current_stays_active
FROM api.stays s
WHERE s.client_id = current_setting('vessel.client_id', false)
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 client_id = current_setting('vessel.client_id', false)
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.client_id = current_setting('vessel.client_id', false)
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
@@ -266,7 +266,7 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
UPDATE api.stays
SET departed = current_stays_departed::timestamp without time zone,
active = current_stays_active
WHERE client_id = current_setting('vessel.client_id', false)
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;
@@ -307,12 +307,17 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
-- Prepare notification, gather user settings
SELECT json_build_object('logbook_name', log_name, 'logbook_link', logbook_rec.id) into log_settings;
user_settings := get_user_settings_from_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;
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;
RAISE DEBUG '-> debug process_logbook_queue_fn log_settings [%]', log_settings;
-- Send notification
PERFORM send_notification_fn('logbook'::TEXT, user_settings::JSONB);
-- Process badges
RAISE NOTICE '--> user_settings [%]', user_settings->>'email'::TEXT;
PERFORM set_config('user.email', user_settings->>'email'::TEXT, false);
PERFORM badges_logbook_fn(logbook_rec.id);
PERFORM badges_geom_fn(logbook_rec.id);
END;
$process_logbook_queue$ LANGUAGE plpgsql;
-- Description
@@ -340,12 +345,12 @@ CREATE OR REPLACE FUNCTION process_stay_queue_fn(IN _id integer) RETURNS void AS
AND longitude IS NOT NULL
AND latitude IS NOT NULL;
-- Ensure the query is successful
IF stay_rec.client_id IS NULL THEN
IF stay_rec.vessel_id IS NULL THEN
RAISE WARNING '-> process_stay_queue_fn invalid stay %', _id;
RETURN;
END IF;
PERFORM set_config('vessel.client_id', stay_rec.client_id, false);
PERFORM set_config('vessel.id', stay_rec.vessel_id, false);
-- geo reverse _lng _lat
_name := reverse_geocode_py_fn('nominatim', stay_rec.longitude::NUMERIC, stay_rec.latitude::NUMERIC);
@@ -372,6 +377,7 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
DECLARE
stay_rec record;
moorage_rec record;
user_settings jsonb;
BEGIN
RAISE NOTICE 'process_moorage_queue_fn';
-- If _id is not NULL
@@ -389,11 +395,13 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
AND latitude IS NOT NULL
AND id = _id;
-- Ensure the query is successful
IF stay_rec.client_id IS NULL THEN
IF stay_rec.vessel_id IS NULL THEN
RAISE WARNING '-> process_moorage_queue_fn invalid stay %', _id;
RETURN;
END IF;
PERFORM set_config('vessel.id', stay_rec.vessel_id, false);
-- Do we have an existing stay within 100m of the new moorage
FOR moorage_rec in
SELECT
@@ -438,9 +446,9 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
END IF;
-- Insert new moorage from stay
INSERT INTO api.moorages
(client_id, name, stay_id, stay_code, stay_duration, reference_count, latitude, longitude, geog)
(vessel_id, name, stay_id, stay_code, stay_duration, reference_count, latitude, longitude, geog)
VALUES (
stay_rec.client_id,
stay_rec.vessel_id,
stay_rec.name,
stay_rec.id,
stay_rec.stay_code,
@@ -451,6 +459,9 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
Geography(ST_MakePoint(stay_rec.longitude, stay_rec.latitude))
);
END IF;
-- Process badges
PERFORM badges_moorages_fn();
END;
$process_moorage_queue$ LANGUAGE plpgsql;
-- Description
@@ -616,7 +627,7 @@ CREATE OR REPLACE FUNCTION process_vessel_queue_fn(IN _email TEXT) RETURNS void
PERFORM set_config('user.email', vessel_rec.owner_email, false);
-- Gather user settings
user_settings := '{"email": "' || vessel_rec.owner_email || '", "boat": "' || vessel_rec.name || '"}';
--user_settings := get_user_settings_from_clientid_fn();
--user_settings := get_user_settings_from_vesselid_fn();
-- Send notification email, pushover
--PERFORM send_notification_fn('vessel'::TEXT, vessel_rec::RECORD);
PERFORM send_email_py_fn('new_vessel'::TEXT, user_settings::JSONB, app_settings::JSONB);
@@ -701,12 +712,12 @@ AS $send_notification$
END IF;
-- Send notification telegram
SELECT (preferences->'telegram'->'from'->'id') IS NOT NULL,preferences['telegram']['from']['id'] INTO _telegram_notifications,_telegram_chat_id
SELECT (preferences->'telegram'->'chat'->'id') IS NOT NULL,preferences['telegram']['chat']['id'] INTO _telegram_notifications,_telegram_chat_id
FROM auth.accounts a
WHERE a.email = user_settings->>'email'::TEXT;
RAISE NOTICE '--> send_notification_fn telegram_notifications [%]', _telegram_notifications;
-- If telegram app settings set and if telegram user settings set
IF app_settings['app.telegram_bot_token'] IS NOT NULL AND _telegram_notifications IS True THEN
IF app_settings['app.telegram_bot_token'] IS NOT NULL AND _telegram_notifications IS True AND _phone_notifications IS True THEN
SELECT json_build_object('telegram_chat_id', _telegram_chat_id) into telegram_settings;
SELECT user_settings::JSONB || telegram_settings::JSONB into user_settings;
--RAISE NOTICE '--> send_notification_fn user_settings + telegram [%]', user_settings;
@@ -719,17 +730,17 @@ COMMENT ON FUNCTION
public.send_notification_fn
IS 'Send notifications via email, pushover, telegram to user base on user preferences';
DROP FUNCTION IF EXISTS get_user_settings_from_clientid_fn;
CREATE OR REPLACE FUNCTION get_user_settings_from_clientid_fn(
IN clientid TEXT,
DROP FUNCTION IF EXISTS get_user_settings_from_vesselid_fn;
CREATE OR REPLACE FUNCTION get_user_settings_from_vesselid_fn(
IN vesselid TEXT,
OUT user_settings JSONB
) RETURNS JSONB
AS $get_user_settings_from_clientid$
AS $get_user_settings_from_vesselid$
DECLARE
BEGIN
-- If client_id is not NULL
IF clientid IS NULL OR clientid = '' THEN
RAISE WARNING '-> get_user_settings_from_clientid_fn invalid input %', clientid;
-- If vessel_id is not NULL
IF vesselid IS NULL OR vesselid = '' THEN
RAISE WARNING '-> get_user_settings_from_vesselid_fn invalid input %', vesselid;
END IF;
SELECT
json_build_object(
@@ -737,91 +748,325 @@ AS $get_user_settings_from_clientid$
'recipient', a.first,
'email', v.owner_email,
'settings', a.preferences,
'pushover_key', a.preferences->'pushover_key',
'badges', a.preferences->'badges'
'pushover_key', a.preferences->'pushover_key'
--'badges', a.preferences->'badges'
) INTO user_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.mmsi = v.mmsi
AND m.client_id = clientid
WHERE m.vessel_id = v.vessel_id
AND m.vessel_id = vesselid
AND lower(a.email) = lower(v.owner_email);
PERFORM set_config('user.email', user_settings->>'email'::TEXT, false);
PERFORM set_config('user.recipient', user_settings->>'recipient'::TEXT, false);
END;
$get_user_settings_from_clientid$ LANGUAGE plpgsql;
$get_user_settings_from_vesselid$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.get_user_settings_from_clientid_fn
IS 'get user settings details from a clientid, initiate for notifications';
public.get_user_settings_from_vesselid_fn
IS 'get user settings details from a vesselid initiate for notifications';
DROP FUNCTION IF EXISTS set_vessel_settings_from_clientid_fn;
CREATE OR REPLACE FUNCTION set_vessel_settings_from_clientid_fn(
IN clientid TEXT,
DROP FUNCTION IF EXISTS set_vessel_settings_from_vesselid_fn;
CREATE OR REPLACE FUNCTION set_vessel_settings_from_vesselid_fn(
IN vesselid TEXT,
OUT vessel_settings JSONB
) RETURNS JSONB
AS $set_vessel_settings_from_clientid$
AS $set_vessel_settings_from_vesselid$
DECLARE
BEGIN
-- If client_id is not NULL
IF clientid IS NULL OR clientid = '' THEN
RAISE WARNING '-> set_vessel_settings_from_clientid_fn invalid input %', clientid;
-- If vessel_id is not NULL
IF vesselid IS NULL OR vesselid = '' THEN
RAISE WARNING '-> set_vessel_settings_from_vesselid_fn invalid input %', vesselid;
END IF;
SELECT
json_build_object(
'name' , v.name,
'mmsi', v.mmsi,
'vessel_id', v.vesselid,
'client_id', m.client_id
) INTO vessel_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.mmsi = v.mmsi
AND m.client_id = clientid;
PERFORM set_config('vessel.mmsi', vessel_rec.mmsi, false);
PERFORM set_config('vessel.name', vessel_rec.name, false);
PERFORM set_config('vessel.client_id', vessel_rec.client_id, false);
WHERE m.vessel_id = v.vessel_id
AND m.vessel_id = vesselid;
PERFORM set_config('vessel.name', vessel_settings->>'name'::TEXT, false);
PERFORM set_config('vessel.client_id', vessel_settings->>'client_id'::TEXT, false);
PERFORM set_config('vessel.id', vessel_settings->>'vessel_id'::TEXT, false);
END;
$set_vessel_settings_from_clientid$ LANGUAGE plpgsql;
$set_vessel_settings_from_vesselid$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.set_vessel_settings_from_clientid_fn
IS 'set_vessel settings details from a clientid, initiate for process queue functions';
public.set_vessel_settings_from_vesselid_fn
IS 'set_vessel settings details from a vesselid, initiate for process queue functions';
create function public.process_badge_queue_fn() RETURNS void AS $process_badge_queue$
declare
badge_rec record;
badges_arr record;
---------------------------------------------------------------------------
-- Badges
--
CREATE OR REPLACE FUNCTION public.badges_logbook_fn(IN logbook_id integer) RETURNS VOID AS $badges_logbook$
DECLARE
_badges jsonb;
_exist BOOLEAN := null;
total integer;
max_wind_speed integer;
distance integer;
badge text;
user_settings jsonb;
BEGIN
-- Helmsman = first log entry
SELECT (preferences->'badges'->'Helmsman') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false THEN
-- is first logbook?
select count(*) into total from api.logbook l where vessel_id = current_setting('vessel.id', false);
if total >= 1 then
-- Add badge
badge := '{"Helmsman": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Helmsman"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Wake Maker = windspeeds above 15kts
SELECT (preferences->'badges'->'Wake Maker') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
--RAISE WARNING '-> Wake Maker %', _exist;
if _exist is false then
-- is 15 knot+ logbook?
select l.max_wind_speed into max_wind_speed from api.logbook l where l.id = logbook_id AND l.max_wind_speed >= 15 and vessel_id = current_setting('vessel.id', false);
--RAISE WARNING '-> Wake Maker max_wind_speed %', max_wind_speed;
if max_wind_speed >= 15 then
-- Create badge
badge := '{"Wake Maker": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
--RAISE WARNING '-> Wake Maker max_wind_speed badge %', badge;
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
--RAISE WARNING '-> Wake Maker max_wind_speed badge % %', badge, _badges;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Wake Maker"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Stormtrooper = windspeeds above 30kts
SELECT (preferences->'badges'->'Stormtrooper') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
--RAISE WARNING '-> Stormtrooper %', _exist;
select l.max_wind_speed into max_wind_speed from api.logbook l where l.id = logbook_id AND l.max_wind_speed >= 30 and vessel_id = current_setting('vessel.id', false);
--RAISE WARNING '-> Stormtrooper max_wind_speed %', max_wind_speed;
if max_wind_speed >= 30 then
-- Create badge
badge := '{"Stormtrooper": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
--RAISE WARNING '-> Stormtrooper max_wind_speed badge %', badge;
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- RAISE WARNING '-> Wake Maker max_wind_speed badge % %', badge, _badges;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Stormtrooper"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Navigator Award = one logbook with distance over 100NM
SELECT (preferences->'badges'->'Navigator Award') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
select l.distance into distance from api.logbook l where l.id = logbook_id AND l.distance >= 100 and vessel_id = current_setting('vessel.id', false);
if distance >= 100 then
-- Create badge
badge := '{"Navigator Award": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Navigator Award"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Captain Award = total logbook distance over 1000NM
SELECT (preferences->'badges'->'Captain Award') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
select sum(l.distance) into distance from api.logbook l where vessel_id = current_setting('vessel.id', false);
if distance >= 1000 then
-- Create badge
badge := '{"Captain Award": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Captain Award"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
END;
$badges_logbook$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.badges_logbook_fn
IS 'check for new badges, eg: Helmsman, Wake Maker, Stormtrooper';
CREATE OR REPLACE FUNCTION public.badges_moorages_fn() RETURNS VOID AS $badges_moorages$
DECLARE
_badges jsonb;
_exist BOOLEAN := false;
duration integer;
badge text;
user_settings jsonb;
BEGIN
-- Check and set environment
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
PERFORM set_config('user.email', user_settings->>'email'::TEXT, false);
-- Explorer = 10 days away from home port
SELECT (preferences->'badges'->'Explorer') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
--select sum(m.stay_duration) from api.moorages m where home_flag is false;
SELECT extract(day from (select sum(m.stay_duration) INTO duration FROM api.moorages m WHERE home_flag IS false AND vessel_id = current_setting('vessel.id', false) ));
if duration >= 10 then
-- Create badge
badge := '{"Explorer": {"date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Explorer"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Mooring Pro = 10 nights on buoy!
SELECT (preferences->'badges'->'Mooring Pro') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
-- select sum(m.stay_duration) from api.moorages m where stay_code = 3;
SELECT extract(day from (select sum(m.stay_duration) INTO duration FROM api.moorages m WHERE stay_code = 3 AND vessel_id = current_setting('vessel.id', false) ));
if duration >= 10 then
-- Create badge
badge := '{"Mooring Pro": {"date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Mooring Pro"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Anchormaster = 25 days on anchor
SELECT (preferences->'badges'->'Anchormaster') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
-- select sum(m.stay_duration) from api.moorages m where stay_code = 2;
SELECT extract(day from (select sum(m.stay_duration) INTO duration FROM api.moorages m WHERE stay_code = 2 AND vessel_id = current_setting('vessel.id', false) ));
if duration >= 25 then
-- Create badge
badge := '{"Anchormaster": {"date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || '{"badge": "Anchormaster"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
end if;
END;
$badges_moorages$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.badges_moorages_fn
IS 'check moorages for new badges, eg: Explorer, Mooring Pro, Anchormaster';
CREATE OR REPLACE FUNCTION public.badges_geom_fn(IN logbook_id integer) RETURNS VOID AS $badges_geom$
DECLARE
_badges jsonb;
_exist BOOLEAN := false;
badge text;
marine_rec record;
user_settings jsonb;
badge_tmp text;
begin
SELECT json_array_elements_text((a.preferences->'badges')::json) from auth.accounts a;
FOR badge_rec in
SELECT
name
FROM badges
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
-- found previous stay within 100m of the new moorage
IF moorage_rec.id IS NOT NULL AND moorage_rec.id > 0 THEN
RAISE NOTICE 'Found previous stay within 100m of moorage %', moorage_rec;
EXIT; -- exit loop
END IF;
-- If not generate and insert the new bagde
--RAISE WARNING 'geography_marine [%]', marine_rec.name;
SELECT jsonb_extract_path(a.preferences, 'badges', marine_rec.name) IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
--RAISE WARNING 'geography_marine [%]', _exist;
if _exist is false then
-- Create badge
badge := '{"' || marine_rec.name || '": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) INTO badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
--RAISE WARNING '--> badges_geom_fn [%]', badge;
-- Gather user settings
badge_tmp := '{"badge": "' || marine_rec.name || '"}';
user_settings := get_user_settings_from_vesselid_fn(current_setting('vessel.id', false));
SELECT user_settings::JSONB || badge_tmp::JSONB INTO user_settings;
-- Send notification
PERFORM send_notification_fn('new_badge'::TEXT, user_settings::JSONB);
end if;
END LOOP;
-- Helmsman
-- select count(l.id) api.logbook l where count(l.id) = 1;
-- Wake Maker
-- select max(l.max_wind_speed) api.logbook l where l.max_wind_speed >= 15;
-- Explorer
-- select sum(m.stay_duration) api.stays s where home_flag is false;
-- Mooring Pro
-- select sum(m.stay_duration) api.stays s where stay_code = 3;
-- Anchormaster
-- select sum(m.stay_duration) api.stays s where stay_code = 2;
-- Traveler
-- todo country to country.
-- Stormtrooper
-- select max(l.max_wind_speed) api.logbook l where l.max_wind_speed >= 30;
-- Club Alaska
-- todo country zone
-- Tropical Traveler
-- todo country zone
-- Aloha Award
-- todo pacific zone
-- TODO the sea is big and the world is not limited to the US
END
$process_badge_queue$ language plpgsql;
END;
$badges_geom$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.badges_geom_fn
IS 'check geometry logbook for new badges, eg: Tropic, Alaska, Geographic zone';
---------------------------------------------------------------------------
-- TODO add alert monitoring for Battery
@@ -849,6 +1094,11 @@ BEGIN
--RAISE WARNING 'jwt email %', current_setting('request.jwt.claims', true)::json->>'email';
--RAISE WARNING 'jwt role %', current_setting('request.jwt.claims', true)::json->>'role';
--RAISE WARNING 'cur_user %', current_user;
--TODO SELECT current_setting('request.jwt.uid', true)::json->>'uid' INTO _user_id;
--TODO RAISE WARNING 'jwt user_id %', current_setting('request.jwt.uid', true)::json->>'uid';
--TODO SELECT current_setting('request.jwt.vid', true)::json->>'vid' INTO _vessel_id;
--TODO RAISE WARNING 'jwt vessel_id %', current_setting('request.jwt.vid', true)::json->>'vid';
IF _role = 'user_role' THEN
-- Check the user exist in the accounts table
SELECT * INTO account_rec
@@ -900,7 +1150,7 @@ BEGIN
AND m.vessel_id = v.vessel_id
AND v.owner_email = _email;
-- 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.id [%]', current_setting('vessel.id', false);
--RAISE WARNING 'public.check_jwt() user_role vessel.name [%]', current_setting('vessel.name', false);
@@ -922,7 +1172,7 @@ BEGIN
--PERFORM set_config('vessel.client_id', vessel_rec.client_id, false);
--RAISE WARNING 'public.check_jwt() user_role vessel.mmsi %', current_setting('vessel.mmsi', false);
--RAISE WARNING 'public.check_jwt() user_role vessel.name %', current_setting('vessel.name', false);
--RAISE WARNING 'public.check_jwt() user_role vessel.client_id %', current_setting('vessel.client_id', false);
--RAISE WARNING 'public.check_jwt() user_role vessel.id %', current_setting('vessel.id', false);
ELSIF _role <> 'api_anonymous' THEN
RAISE EXCEPTION 'Invalid role'
USING HINT = 'Stop being so evil and maybe you can log in';

View File

@@ -345,10 +345,10 @@ $urlencode_py$ LANGUAGE plpython3u IMMUTABLE STRICT;
-- python
-- https://ipapi.co/
DROP FUNCTION IF EXISTS reverse_geoip_py_fn;
CREATE OR REPLACE FUNCTION reverse_geoip_py_fn(IN _ip TEXT) RETURNS void
CREATE OR REPLACE FUNCTION reverse_geoip_py_fn(IN _ip TEXT) RETURNS JSONB
AS $reverse_geoip_py$
"""
TODO
Return ipapi.co ip details
"""
import requests
import json
@@ -358,13 +358,15 @@ AS $reverse_geoip_py$
r = requests.get(url)
#print(r.text)
# Return something boolean?
#plpy.notice('Sent successfully to [{}] [{}]'.format(r.text, r.status_code))
#plpy.notice('IP [{}] [{}]'.format(_ip, r.status_code))
if r.status_code == 200:
plpy.notice('Sent successfully to [{}] [{}]'.format(r.text, r.status_code))
#plpy.notice('Got [{}] [{}]'.format(r.text, r.status_code))
return r.text;
else:
plpy.error('Failed to send')
return None
$reverse_geoip_py$ TRANSFORM FOR TYPE jsonb LANGUAGE plpython3u;
plpy.error('Failed to get ip details')
return '{}'
$reverse_geoip_py$ LANGUAGE plpython3u;
-- Description
COMMENT ON FUNCTION
public.reverse_geoip_py_fn
@@ -403,6 +405,8 @@ AS $geojson_py$
#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)

View File

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

View File

@@ -9,9 +9,19 @@ select current_database();
\c signalk
-- Link auth.vessels with api.metadata
ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
--ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
ALTER TABLE api.metadata ADD FOREIGN KEY (vessel_id) REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
COMMENT ON COLUMN api.metadata.vessel_id IS 'Link auth.vessels with api.metadata';
-- Link auth.vessels with auth.accounts
--ALTER TABLE auth.vessels ADD user_id TEXT NOT NULL REFERENCES auth.accounts(user_id) ON DELETE RESTRICT;
--COMMENT ON COLUMN auth.vessels.user_id IS 'Link auth.vessels with auth.accounts';
--COMMENT ON COLUMN auth.vessels.vessel_id IS 'Vessel identifier. Link auth.vessels with api.metadata';
-- REFERENCE ship type with AIS type ?
-- REFERENCE mmsi MID with country ?
-- List vessel
--TODO add geojson with position
DROP VIEW IF EXISTS api.vessels_view;
@@ -63,18 +73,15 @@ AS $vessel$
DECLARE
BEGIN
SELECT
json_build_object(
jsonb_build_object(
'name', v.name,
'mmsi', coalesce(v.mmsi, null),
'created_at', v.created_at::timestamp(0),
'last_contact', coalesce(m.time, null),
'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null)
)
)::jsonb || api.vessel_details_fn()::jsonb
INTO vessel
FROM auth.vessels v, api.metadata m,
( SELECT
t.*
FROM (
( select
current_setting('vessel.name') as name,
time,
@@ -87,10 +94,8 @@ AS $vessel$
WHERE
latitude 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
WHERE
m.vessel_id = current_setting('vessel.id')
@@ -135,7 +140,9 @@ AS $version$
FROM app_settings
WHERE name = 'app.version';
RETURN json_build_object('api_version', _appv,
'sys_version', _sysv);
'sys_version', _sysv,
'timescaledb', (SELECT extversion as timescaledb FROM pg_extension WHERE extname='timescaledb'),
'postgis', (SELECT extversion as postgis FROM pg_extension WHERE extname='postgis'));
END;
$version$ language plpgsql security definer;
-- Description
@@ -193,3 +200,27 @@ $update_user_preferences$ language plpgsql security definer;
COMMENT ON FUNCTION
api.update_user_preferences_fn
IS 'Update user preferences jsonb key pair value';
DROP FUNCTION IF EXISTS api.vessel_details_fn;
CREATE OR REPLACE FUNCTION api.vessel_details_fn() RETURNS JSON AS
$vessel_details$
DECLARE
BEGIN
RETURN ( WITH tbl AS (
SELECT mmsi,ship_type,length,beam,height FROM api.metadata WHERE vessel_id = current_setting('vessel.id', false)
)
SELECT json_build_object(
'ship_type', (SELECT ais.description FROM aistypes ais, tbl WHERE t.ship_type = ais.id),
'country', (SELECT mid.country FROM mid, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = mid.id),
'alpha_2', (SELECT o.alpha_2 FROM mid m, iso3166 o, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = m.id AND m.country_id = o.id),
'length', t.ship_type,
'beam', t.beam,
'height', t.height)
FROM tbl t
);
END;
$vessel_details$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
api.vessel_details_fn
IS 'Return vessel details such as metadata (length,beam,height), ais type and country name and country iso3166-alpha-2';

View File

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

View File

@@ -30,12 +30,14 @@ grant execute on function api.recover(text) to api_anonymous;
grant execute on function api.reset(text,text,text) to api_anonymous;
-- explicitly limit EXECUTE privileges to pgrest db-pre-request function
grant execute on function public.check_jwt() to api_anonymous;
-- explicitly limit EXECUTE privileges to only telegram bot auth function
grant execute on function api.bot(text,bigint) to api_anonymous;
-- explicitly limit EXECUTE privileges to only telegram jwt auth function
grant execute on function api.telegram(bigint,text) to api_anonymous;
-- explicitly limit EXECUTE privileges to only pushover subscription validation function
grant execute on function api.email_fn(text) to api_anonymous;
grant execute on function api.pushover_fn(text,text) to api_anonymous;
grant execute on function api.telegram_fn(text,text) to api_anonymous;
grant execute on function api.telegram_otp_fn(text) to api_anonymous;
--grant execute on function api.generate_otp_fn(text) to api_anonymous;
-- authenticator
-- login role
@@ -183,19 +185,19 @@ CREATE POLICY admin_all ON api.metadata TO current_user
WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.metadata TO vessel_role
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true);
-- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.metadata TO user_role
USING (client_id = current_setting('vessel.client_id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow scheduler to update and select based on the client_id
USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.metadata TO scheduler
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on email
CREATE POLICY grafana_role ON api.metadata TO grafana
USING (client_id = current_setting('vessel.client_id', false))
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
@@ -209,19 +211,19 @@ CREATE POLICY admin_all ON api.metrics TO current_user
WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.metrics TO vessel_role
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true);
-- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.metrics TO user_role
USING (client_id = current_setting('vessel.client_id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow scheduler to update and select based on the client_id
USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.metrics TO scheduler
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the vessel.id
CREATE POLICY grafana_role ON api.metrics TO grafana
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
@@ -233,19 +235,19 @@ CREATE POLICY admin_all ON api.logbook TO current_user
WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.logbook TO vessel_role
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true);
-- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.logbook TO user_role
USING (client_id = current_setting('vessel.client_id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow scheduler to update and select based on the client_id
USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.logbook TO scheduler
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the vessel.id
CREATE POLICY grafana_role ON api.logbook TO grafana
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
@@ -256,19 +258,19 @@ CREATE POLICY admin_all ON api.stays TO current_user
WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.stays TO vessel_role
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true);
-- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.stays TO user_role
USING (client_id = current_setting('vessel.client_id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow scheduler to update and select based on the client_id
USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the vessel_id
CREATE POLICY api_scheduler_role ON api.stays TO scheduler
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the vessel_id
CREATE POLICY grafana_role ON api.stays TO grafana
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
@@ -279,19 +281,19 @@ CREATE POLICY admin_all ON api.moorages TO current_user
WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.moorages TO vessel_role
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true);
-- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.moorages TO user_role
USING (client_id = current_setting('vessel.client_id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow scheduler to update and select based on the client_id
USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the vessel_id
CREATE POLICY api_scheduler_role ON api.moorages TO scheduler
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the vessel_id
CREATE POLICY grafana_role ON api.moorages TO grafana
USING (client_id = current_setting('vessel.client_id', false))
USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table

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.11
0.2.0

File diff suppressed because one or more lines are too long