feat: add pre-upgrade and post-upgrade DB migration hooks

Dockerfile: add postgresql-client and bake db-customization/ SQL scripts
into the image at /vnc/db-customization/.

config/migrate.sh: detection + migration script with two modes:
- pre-upgrade: runs idempotent prosody-13-new-deployment.sql on 13.0.x
  databases; skips 0.11.6 (unsafe pre-upgrade) and new deployments.
- post-upgrade: waits for Prosody table, then runs the appropriate
  scripts — full 0.11.6->13.0.6 migration (rules-triggers + migration-once
  + new-deployment) or idempotent drift correction for 13.0.x.

Helm chart: two Job templates (db-migration-pre-upgrade.yaml,
db-migration-post-upgrade.yaml) gated by dbMigration.enabled (default
true). Both reuse the Prosody image and DB credentials from existing
values. backoffLimit: 0, hook-delete-policy: hook-succeeded.

Also tracks the db-customization SQL files (previously untracked, now
referenced by the Dockerfile ADD).

Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
This commit is contained in:
2026-07-15 17:59:09 +02:00
parent 86fdfbb986
commit e1c78e7358
10 changed files with 4882 additions and 4 deletions
+2 -1
View File
@@ -40,7 +40,7 @@ RUN apk update && apk upgrade && \
apk add gettext lua5.4 lua5.4-socket lua5.4-dbi-postgresql lua5.4-expat lua5.4-sql-postgres \ apk add gettext lua5.4 lua5.4-socket lua5.4-dbi-postgresql lua5.4-expat lua5.4-sql-postgres \
lua5.4-filesize lua5.4-lpeg lua5.4-hiredis lua5.4-filesystem lua5.4-ldap \ lua5.4-filesize lua5.4-lpeg lua5.4-hiredis lua5.4-filesystem lua5.4-ldap \
lua5.4-sec lua5.4-lzlib lua5.4-cjson \ lua5.4-sec lua5.4-lzlib lua5.4-cjson \
libidn-dev libidn icu libpq expat \ libidn-dev libidn icu libpq postgresql-client expat \
openssl nodejs nagios-plugins-tcp && \ openssl nodejs nagios-plugins-tcp && \
rm -rf /var/cache/apk/* rm -rf /var/cache/apk/*
@@ -56,6 +56,7 @@ RUN adduser -D -u 1001 -S -h /var/lib/prosody -H -G prosody prosody
ADD vnctalk /vnc/vnctalk ADD vnctalk /vnc/vnctalk
RUN cd /vnc/vnctalk && cp -Rp * /usr/local/lib/prosody/modules/ RUN cd /vnc/vnctalk && cp -Rp * /usr/local/lib/prosody/modules/
ADD config /vnc/config ADD config /vnc/config
ADD db-customization /vnc/db-customization
USER prosody USER prosody
ENV __FLUSH_LOG=yes ENV __FLUSH_LOG=yes
+151 -3
View File
@@ -1,5 +1,153 @@
#!/bin/sh #!/bin/sh
# set -e
# dummy migrate script - preparation for new chart
echo "dummy - all done" MODE="${1:-pre-upgrade}"
SQL_DIR="/vnc/db-customization"
log() {
echo "[migrate:$MODE] $*"
}
# Map Prosody env vars to PostgreSQL env vars
export PGHOST="${prosodyDBhost}"
export PGPORT="${prosodyDBport:-5432}"
export PGUSER="${prosodyDBuser}"
export PGDATABASE="${prosodyDBname}"
export PGPASSWORD="${prosodyDBpass}"
PSQL="psql -v ON_ERROR_STOP=1"
# ---------------------------------------------------------------------------
# Detection helpers
# ---------------------------------------------------------------------------
prosody_table_exists() {
$PSQL -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'prosody')" 2>/dev/null | tr -d '[:space:]'
}
derived_tables_exist() {
$PSQL -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'processed_messages')" 2>/dev/null | tr -d '[:space:]'
}
room_membership_view_exists() {
$PSQL -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.views WHERE table_name = 'room_membership')" 2>/dev/null | tr -d '[:space:]'
}
room_membership_uses_old_format() {
$PSQL -tAc "SELECT pg_get_viewdef('room_membership'::regclass, true)" 2>/dev/null | grep -q "_affiliations"
}
wait_for_prosody_table() {
local max="${1:-60}"
local i=0
while [ "$i" -lt "$max" ]; do
if [ "$(prosody_table_exists)" = "t" ]; then
return 0
fi
log "waiting for prosody table to exist ($i/$max)..."
sleep 2
i=$((i + 2))
done
return 1
}
# ---------------------------------------------------------------------------
# Migration runners
# ---------------------------------------------------------------------------
run_new_deployment() {
log "running prosody-13-new-deployment.sql (idempotent)..."
$PSQL -f "$SQL_DIR/prosody-13-new-deployment.sql"
log "prosody-13-new-deployment.sql completed."
}
run_rules_triggers() {
log "running prosody-13-rules-triggers.sql..."
$PSQL -f "$SQL_DIR/prosody-13-rules-triggers.sql"
log "prosody-13-rules-triggers.sql completed."
}
run_migration_once() {
log "running prosody-13-migration-once.sql..."
$PSQL -f "$SQL_DIR/prosody-13-migration-once.sql"
log "prosody-13-migration-once.sql completed."
}
# ---------------------------------------------------------------------------
# Pre-upgrade mode
# ---------------------------------------------------------------------------
migrate_pre_upgrade() {
if [ "$(prosody_table_exists)" != "t" ]; then
log "prosody table does not exist. Prosody has not started yet. Skipping."
return 0
fi
if [ "$(derived_tables_exist)" != "t" ]; then
log "no derived tables found. Skipping (will be handled post-install)."
return 0
fi
if [ "$(room_membership_view_exists)" = "t" ]; then
if room_membership_uses_old_format; then
log "WARNING: 0.11.6 format detected (room_membership uses _affiliations)."
log "Running 13.0 rules/triggers against 0.11.6 data is unsafe pre-upgrade."
log "The migration will run in the post-upgrade hook after the new image starts."
return 0
fi
fi
log "13.0 format detected. Running idempotent schema check..."
run_new_deployment
}
# ---------------------------------------------------------------------------
# Post-upgrade mode
# ---------------------------------------------------------------------------
migrate_post_upgrade() {
if ! wait_for_prosody_table 120; then
log "prosody table still does not exist after 120s. Skipping."
return 0
fi
if [ "$(derived_tables_exist)" != "t" ]; then
log "new deployment detected (no derived tables)."
run_new_deployment
return 0
fi
if [ "$(room_membership_view_exists)" = "t" ] && room_membership_uses_old_format; then
log "0.11.6 format detected (room_membership uses _affiliations)."
log "Running full 0.11.6 -> 13.0.6 migration..."
run_rules_triggers
run_migration_once
run_new_deployment
log "0.11.6 -> 13.0.6 migration completed."
return 0
fi
log "13.0 format detected. Running idempotent schema check..."
run_new_deployment
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
log "starting (mode=$MODE)"
case "$MODE" in
pre-upgrade)
migrate_pre_upgrade
;;
post-upgrade)
migrate_post_upgrade
;;
*)
log "ERROR: unknown mode '$MODE'. Use 'pre-upgrade' or 'post-upgrade'."
exit 1
;;
esac
log "done."
@@ -0,0 +1,194 @@
-- Migration script — Prosody 0.11.6 -> 13.0.6
--
-- Run EXACTLY ONCE, after the Prosody image has been upgraded to 13.0.6 and
-- AFTER the existing db-customization scripts
-- (prosody-queries-noowner.sql.notifyfix + prosody-trigger-noowner.sql) have
-- been applied to this database.
--
-- This script:
-- 1. Reconciles derived tables with the new MUC storage layout (affiliations
-- stored as one row per bare JID in the `prosody` `config` store, no
-- `_affiliations` / `_occupants` keys).
-- 2. Removes the now-obsolete `room_nicknames` view and the
-- `update_room_nick_jid_map` rule that depended on it.
-- 3. Is a NO-OP if run a second time (all statements are idempotent), but it
-- is intended to be run only once.
--
-- Prerequisite: the `prosody` and `prosodyarchive` tables must already exist
-- (created by the 13.0.6 storage backend / the regular db-customization
-- scripts). This script aborts if they do not.
--
-- The `room_nick_jid_map` rebuild assumes all allowed clients use their bare
-- JID as their nickname in rooms, so the map can be created purely from
-- affiliations (nickname = "<room@host>/<bare-jid>").
\set ON_ERROR_STOP on
-- Guard: bail out if the Prosody tables are missing.
do $$
begin
if not exists (select 1 from information_schema.tables where table_name = 'prosody') then
raise exception 'Table "prosody" does not exist. Apply the regular db-customization scripts first.';
end if;
if not exists (select 1 from information_schema.tables where table_name = 'prosodyarchive') then
raise exception 'Table "prosodyarchive" does not exist. Apply the regular db-customization scripts first.';
end if;
end $$;
-- ---------------------------------------------------------------------------
-- 1. Drop obsolete objects that referenced the old `_affiliations` / `_occupants` rows
-- ---------------------------------------------------------------------------
-- The `room_nicknames` view read `key='_occupants'`, which no longer exists in
-- 13.0.6 (occupant state moved to the `state` store and is only populated on
-- graceful shutdown, so it is not usable for live nickname resolution).
drop view if exists room_nicknames cascade;
-- `update_room_nick_jid_map` refreshed `room_nick_jid_map` from `room_nicknames`
-- on every `muc_log` insert; with the view gone it is dead.
drop rule if exists update_room_nick_jid_map on prosodyarchive cascade;
-- ---------------------------------------------------------------------------
-- 2. Backfill `room_nick_jid_map` from the new per-affiliation rows
-- ---------------------------------------------------------------------------
-- Remove rows for affiliations that no longer exist (rooms whose members were
-- changed under 13.0.6 before this migration, or stale entries from the old
-- `_occupants`-based population).
delete from room_nick_jid_map
where room_name in (
select p.user || '@' || p.host
from prosody p
where p.store = 'config' and p.host like 'conference.%'
group by p.user, p.host
)
and not exists (
select 1 from prosody p2
where p2.store = 'config'
and p2.host like 'conference.%'
and p2.key like '%@%'
and p2.value in ('owner','admin','member','outcast','none')
and p2.user || '@' || p2.host = room_nick_jid_map.room_name
and p2.key = room_nick_jid_map.user_jid
);
-- Upsert current members. Nickname = "<room@host>/<bare-jid>" (bare JID is the
-- only nickname used by allowed clients). room_nick_jid_map has an INSERT
-- INSTEAD rule (insert_room_nick_jid_map) so ON CONFLICT cannot be used;
-- insert rows that are missing, update the rest.
insert into room_nick_jid_map (room_name, user_jid, nickname, since)
select
p.user || '@' || p.host as room_name,
p.key as user_jid,
(p.user || '@' || p.host) || '/' || p.key as nickname,
(extract(epoch from now())::integer - 60) as since
from prosody p
where p.store = 'config'
and p.host like 'conference.%'
and p.key like '%@%'
and p.value in ('owner','admin','member','outcast','none')
and not exists (
select 1 from room_nick_jid_map r
where r.room_name = p.user || '@' || p.host
and r.user_jid = p.key
and r.nickname = (p.user || '@' || p.host) || '/' || p.key
);
update room_nick_jid_map r
set since = (extract(epoch from now())::integer - 60)
from prosody p
where p.store = 'config'
and p.host like 'conference.%'
and p.key like '%@%'
and p.value in ('owner','admin','member','outcast','none')
and r.room_name = p.user || '@' || p.host
and r.user_jid = p.key
and r.nickname = (p.user || '@' || p.host) || '/' || p.key;
-- ---------------------------------------------------------------------------
-- 3. Reconcile `group_owners` with the new per-affiliation rows
-- ---------------------------------------------------------------------------
-- Drop owners that are no longer present as `value='owner'` affiliation rows.
delete from group_owners go
where not exists (
select 1 from prosody p
where p.store = 'config'
and p.host like 'conference.%'
and p.key like '%@%'
and p.value = 'owner'
and p.user || '@' || p.host = go.room
and p.key = go.owner
);
-- Upsert current owners (one row per room; pick the first owner by JID).
-- group_owners has an INSERT INSTEAD rule (upsert_group_owners) so ON CONFLICT
-- cannot be used; insert rooms that are missing, update the rest.
insert into group_owners (room, owner, created, updated)
select
sub.room,
sub.owner,
extract(epoch from now())::integer as created,
extract(epoch from now())::integer as updated
from (
select
p.user || '@' || p.host as room,
min(p.key) as owner
from prosody p
where p.store = 'config'
and p.host like 'conference.%'
and p.key like '%@%'
and p.value = 'owner'
group by p.user, p.host
) sub
where not exists (
select 1 from group_owners go where go.room = sub.room
);
update group_owners go
set owner = sub.owner,
updated = extract(epoch from now())::integer
from (
select
p.user || '@' || p.host as room,
min(p.key) as owner
from prosody p
where p.store = 'config'
and p.host like 'conference.%'
and p.key like '%@%'
and p.value = 'owner'
group by p.user, p.host
) sub
where go.room = sub.room
and go.owner <> sub.owner;
-- ---------------------------------------------------------------------------
-- 4. Mark `recent_history_table` rows for users no longer affiliated
-- ---------------------------------------------------------------------------
-- Members removed from a room before this migration may still have non-deleted
-- recent-history rows. Mark them deleted now (the rewritten trigger in the
-- idempotent script will keep this in sync going forward).
update recent_history_table rht
set deleted = true
where rht.type = 'groupchat'
and rht.target like '%@conference.%'
and not exists (
select 1 from prosody p
where p.store = 'config'
and p.host like 'conference.%'
and p.key like '%@%'
and p.value in ('owner','admin','member','outcast','none')
and p.user || '@' || p.host = rht.target
and p.key = rht.username
)
and exists (
select 1 from prosody p3
where p3.store = 'config'
and p3.host like 'conference.%'
and p3.user || '@' || p3.host = rht.target
);
-- Done. The derived tables now match the 13.0.6 storage layout. Going forward
-- the rewritten rules/triggers from `prosody-13-migration-rules-triggers.sql`
-- keep them consistent.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,274 @@
-- Idempotent rules, views and triggers for Prosody 13.0.6
--
-- This script creates the rules, views and trigger functions that had to be
-- adapted to the new MUC storage layout (affiliations stored as one row per
-- bare JID; `_affiliations` / `_occupants` keys gone).
--
-- It is IDEMPOTENT: every object is created with `or replace` / `drop if
-- exists` first, so it can be re-run safely on any 13.0.6 database.
--
-- It only creates the objects that CHANGED vs the 0.11.6 scripts
-- (`prosody-queries-noowner.sql.notifyfix` + `prosody-trigger-noowner.sql`).
-- It does NOT recreate the many rules/views/triggers that are unaffected by
-- the storage change — those remain defined by the original two scripts.
--
-- The `room_nick_jid_map` maintenance assumes all allowed clients use their
-- bare JID as their nickname in rooms, so the map is derived purely from
-- affiliations (nickname = "<room@host>/<bare-jid>").
--
-- Prerequisite: the `prosody` and `prosodyarchive` tables must already exist.
-- The script aborts if they do not.
\set ON_ERROR_STOP on
-- Guard: do nothing if the Prosody tables are missing.
do $$
begin
if not exists (select 1 from information_schema.tables where table_name = 'prosody') then
raise exception 'Table "prosody" does not exist. Apply the regular db-customization scripts first.';
end if;
if not exists (select 1 from information_schema.tables where table_name = 'prosodyarchive') then
raise exception 'Table "prosodyarchive" does not exist. Apply the regular db-customization scripts first.';
end if;
end $$;
-- ===========================================================================
-- VIEWS
-- ===========================================================================
-- `room_membership`: was `where prosody.key='_affiliations'` with
-- `jsonb_object_keys(value::jsonb)`. Affiliations are now one row per bare JID
-- (`key=<jid>`, `value=<affiliation string>`). Column shape is preserved so
-- all consumers (update_muc_recent, insert_unread_message_ids_muc,
-- insert_unread_mention_ids_muc, fprocess_messages muc_log branches) work
-- unchanged.
create or replace view room_membership as
select
prosody.key as username,
''::text as user_room_nickname,
prosody.host as host,
prosody.user as room
from prosody
where prosody.store = 'config'
and prosody.host like 'conference.%'
and prosody.key like '%@%'
and prosody.value in ('owner','admin','member','outcast','none');
-- `room_nicknames` is obsolete (read `key='_occupants'`, which no longer
-- exists). Drop it so stale references do not linger. `room_nick_jid_map`
-- (maintained below from affiliations) replaces it for nickname resolution.
drop view if exists room_nicknames cascade;
-- ===========================================================================
-- TRIGGER: group_owners maintenance (replaces the old `update_group_owners` rule)
-- ===========================================================================
--
-- Was a rule `on insert to prosody where key='_affiliations' ...`. Now owners
-- are rows with `key=<bare jid>`, `value='owner'`. A trigger is used instead
-- of a rule because `group_owners` already has an INSERT INSTEAD rule
-- (`upsert_group_owners`); combining DO-INSTEAD and DO rules on the same
-- insert produces ambiguous/conflicting behaviour. The trigger fires on the
-- `_data` row (always written on every room save) and reconciles
-- `group_owners` for the whole room, mirroring how `fupdate_room_nick_jid`
-- rebuilds `room_nick_jid_map`.
create or replace function fupdate_group_owners()
returns trigger AS
$BODY$
DECLARE
_room text;
BEGIN
_room := NEW.user || '@' || NEW.host;
-- remove owners that are no longer affiliated as owner
delete from group_owners
where room = _room
and owner not in (
select key from prosody
where store = 'config'
and host = NEW.host
and "user" = NEW.user
and key like '%@%'
and value = 'owner'
);
-- upsert current owners. group_owners has an INSERT INSTEAD rule
-- (upsert_group_owners) so ON CONFLICT cannot be used; insert the room
-- if missing, otherwise update the owner.
insert into group_owners (room, owner, created, updated)
select
_room,
sub.owner,
extract(epoch from now())::integer,
extract(epoch from now())::integer
from (
select key as owner
from prosody
where store = 'config'
and host = NEW.host
and "user" = NEW.user
and key like '%@%'
and value = 'owner'
order by key
limit 1
) sub
where not exists (
select 1 from group_owners go where go.room = _room
);
update group_owners
set owner = sub.owner,
updated = extract(epoch from now())::integer
from (
select key as owner
from prosody
where store = 'config'
and host = NEW.host
and "user" = NEW.user
and key like '%@%'
and value = 'owner'
order by key
limit 1
) sub
where group_owners.room = _room
and group_owners.owner <> sub.owner;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
drop trigger if exists update_group_owners on prosody;
create trigger update_group_owners after insert on prosody
for each row
when (NEW.store = 'config' and NEW.key = '_data' and NEW.host like 'conference.%')
execute procedure fupdate_group_owners();
-- ===========================================================================
-- RULES ON `prosodyarchive`
-- ===========================================================================
-- `update_room_nick_jid_map` refreshed `room_nick_jid_map` from the now-dropped
-- `room_nicknames` view on every `muc_log` insert. With `_occupants` gone it
-- is dead; `room_nick_jid_map` is maintained by the `fupdate_room_nick_jid`
-- trigger on the `config` store plus the (unchanged) invite rules.
drop rule if exists update_room_nick_jid_map on prosodyarchive cascade;
-- ===========================================================================
-- TRIGGER FUNCTION: fupdate_room_nick_jid
-- ===========================================================================
-- Was: trigger `when (NEW.key='_affiliations')`, body rebuilt the room's map
-- from `jsonb_object_keys(NEW.value::jsonb)`. Now: there is no `_affiliations`
-- row; affiliations are one row per bare JID.
--
-- Approach: fire on the `_data` row, which is always written on every room
-- save (it is always present in `freeze()` output), and rebuild
-- `room_nick_jid_map` from the current set of affiliation rows for the room.
-- (The kv `set` DELETEs then re-inserts all rows in one transaction; the
-- `_data` row is inserted after the affiliation rows, so they are already
-- present when this trigger fires.)
--
-- Nickname = "<room@host>/<bare-jid>" — bare JID is the only nickname used by
-- allowed clients, so the map is derived purely from affiliations.
create or replace function fupdate_room_nick_jid()
returns trigger AS
$BODY$
DECLARE
_room text;
BEGIN
_room := NEW.user || '@' || NEW.host;
-- remove mapping entries for users no longer affiliated
delete from room_nick_jid_map
where room_name = _room
and user_jid not in (
select key from prosody
where store = 'config'
and host = NEW.host
and "user" = NEW.user
and key like '%@%'
and value in ('owner','admin','member','outcast','none')
);
-- mark recent_history rows for dropped members as deleted
update recent_history_table
set deleted = true
where target = _room
and username not in (
select key from prosody
where store = 'config'
and host = NEW.host
and "user" = NEW.user
and key like '%@%'
and value in ('owner','admin','member','outcast','none')
);
-- upsert current members (nickname = room/bare-jid). room_nick_jid_map has
-- an INSERT INSTEAD rule (insert_room_nick_jid_map) so ON CONFLICT cannot
-- be used; insert rows that are missing, update the rest.
insert into room_nick_jid_map (room_name, user_jid, nickname, since)
select
_room,
key,
_room || '/' || key,
(extract(epoch from now())::integer - 60)
from prosody
where store = 'config'
and host = NEW.host
and "user" = NEW.user
and key like '%@%'
and value in ('owner','admin','member','outcast','none')
and not exists (
select 1 from room_nick_jid_map r
where r.room_name = _room
and r.user_jid = prosody.key
and r.nickname = _room || '/' || prosody.key
);
update room_nick_jid_map r
set since = (extract(epoch from now())::integer - 60)
from prosody
where prosody.store = 'config'
and prosody.host = NEW.host
and prosody."user" = NEW.user
and prosody.key like '%@%'
and prosody.value in ('owner','admin','member','outcast','none')
and r.room_name = _room
and r.user_jid = prosody.key
and r.nickname = _room || '/' || prosody.key;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
-- (Re)create the trigger. Drop the old `when (NEW.key='_affiliations')`
-- trigger first; the duplicate function definitions from the 0.11.6 script
-- are consolidated by the `create or replace function` above.
drop trigger if exists update_room_nick_jid on prosody;
create trigger update_room_nick_jid after insert on prosody
for each row
when (NEW.store = 'config' and NEW.key = '_data' and NEW.host like 'conference.%')
execute procedure fupdate_room_nick_jid();
-- ===========================================================================
-- TRIGGER FUNCTION: fupdate_groupchat (UNCHANGED — kept here for completeness)
-- ===========================================================================
--
-- `fupdate_groupchat` reads `key='_data'`, which 13.0.6 still writes with a
-- JSON value containing `subject`. It therefore keeps working as-is. It is
-- already created by `prosody-trigger-noowner.sql`; no change is needed and
-- it is NOT recreated here to avoid drifting from the original definition.
-- ===========================================================================
-- VERIFICATION (optional, commented out)
-- ===========================================================================
-- Inspect the storage layout for a known room to confirm affiliations are
-- stored as one row per bare JID:
--
-- select key, type, value from prosody
-- where store='config' and host='conference.<domain>' and user='<room-node>';
--
-- Expected: rows with key = bare JID and value in
-- (owner, admin, member, outcast, none), plus _jid / _data /
-- _affiliation_data meta rows.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,752 @@
-- for processing inserts with trigger
create table if not exists processed_messages (
sort_id bigint,
body text,
date integer,
"from" text,
"to" text,
owner text,
broadcast text,
"broadcast-sender" text,
room text,
type text,
id text,
receipts text,
x_attachment text,
x_location text,
x_replaceMsgId text,
x_origMessage text,
x_vncConference text,
x_forwardMessage text,
mention text,
PRIMARY KEY (sort_id)
);
create index si2 on processed_messages ("from");
create index si3 on processed_messages ("to");
create index si4 on processed_messages (owner);
create index si5 on processed_messages (broadcast);
create index si6 on processed_messages (room);
create index si7 on processed_messages (type);
create index si8 on processed_messages (id);
alter table processed_messages add column htmlbody text;
alter table processed_messages add column topicid text;
alter table processed_messages add column parent0 text;
alter table processed_messages add column parent text;
alter table processed_messages add column topic text;
alter table processed_messages add column updated integer;
alter table processed_messages add column broadcast_title text;
alter table processed_messages add column group_action text;
alter table processed_messages add column "encrypted" text;
alter table processed_messages add column encryption text;
alter table processed_messages add column reactions text;
alter table processed_messages add column expiry integer;
alter table processed_messages add column starredBy text[];
create index si9 on processed_messages (expiry);
create index di1 on processed_messages (date);
create or replace rule upsert_processed_messages as
on insert to processed_messages where (exists (select 1 from processed_messages where sort_id=NEW.sort_id))
do instead
update processed_messages set
body = NEW.body,
htmlbody = NEW.htmlbody,
topicid = NEW.topicid,
parent0 = NEW.parent0,
parent = NEW.parent,
topic = NEW.topic,
date = NEW.date,
"from" = NEW.from,
"to" = NEW.to,
owner = NEW.owner,
broadcast = NEW.broadcast,
"broadcast-sender" = NEW."broadcast-sender",
room = NEW.room,
type = NEW.type,
id = NEW.id,
receipts = NEW.receipts,
x_attachment = NEW.x_attachment,
x_location = NEW.x_location,
x_replaceMsgId = NEW.x_replaceMsgId,
x_origMessage = NEW.x_origMessage,
x_vncConference = NEW.x_vncConference,
group_action = NEW.group_action,
mention = NEW.mention,
expiry = NEW.expiry
where sort_id=NEW.sort_id;
create table if not exists call_tracking (
callid bigint,
started_at integer,
updated_at integer,
caller text,
conferenceId text,
receipient text,
state text,
PRIMARY KEY (callid)
);
create index cri1 on call_tracking (receipient);
create index csi1 on call_tracking (started_at);
create or replace function fprocess_messages()
returns trigger AS
$BODY$
DECLARE
_body text;
_htmlbody text;
_topic text;
_parent text;
_parent0 text;
_x_attachment text;
_x_location text;
_x_replaceMsgId text;
_original_message text;
_x_conference text;
_x_forwardMessage text;
_group_action text;
_mention text;
_x_encrypted text;
_encryption text;
_message_id text;
_origtarget text;
_from_bare text;
_x_invite text;
_x_broadcast text;
_x_when integer;
_from text;
_to text;
_received_id text;
_broadcast_title text;
_x_conference_scheduler text;
_x_conferencekey text;
_x_conference_from text;
_x_role text;
_x_affiliation text;
BEGIN
_body := (xpath('/message/body/text() | /message/jc:body/text() | /jc:message/jc:body/text()', NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text;
_htmlbody := (xpath('//message/html/* | //message/xim:html/* | //jc:message/xim:html/*', NEW.value::xml, ARRAY[ARRAY['xim', 'http://jabber.org/protocol/xhtml-im'], ARRAY['jc', 'jabber:client']]))[1]::text;
_topic := (xpath('/message/xc:topic/@name | /jc:message/xc:topic/@name', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text;
_parent0 := (xpath('/message/xc:topic/@parent0 | /jc:message/xc:topic/@parent0', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text;
_parent := (xpath('/message/xc:topic/@parent | /jc:message/xc:topic/@parent', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text;
_x_attachment := (xpath('//attachment | //xc:attachment', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
_x_location := (xpath('/message/xc:location | /jc:message/xc:location', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text;
_x_replaceMsgId := (xpath('/message/mc0:replace/@id | /jc:message/mc0:replace/@id', NEW.value::xml, ARRAY[ARRAY['mc0', 'urn:xmpp:message-correct:0'], ARRAY['jc', 'jabber:client']]))[1]::text;
_original_message := (xpath('//originalMessage | //xc:originalMessage', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
_x_conference := (xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
_x_forwardMessage := (xpath('/message/xc:forwardMessage | /jc:message/xc:forwardMessage', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text;
_group_action := (xpath('//group_action/type/text() | //xc:group_action/xc:type/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
_mention := (to_jsonb(xpath('/message/xr:reference/@uri | /jc:message/xr:reference/@uri', NEW.value::xml, ARRAY[ARRAY['xr', 'urn:xmpp:reference:0'], ARRAY['jc', 'jabber:client']])))::jsonb;
_x_encrypted := (xpath('//jc:message/xo:encrypted |//message/xo:encrypted', NEW.value::xml , ARRAY[ARRAY['jc','jabber:client'],ARRAY['xo','urn:xmpp:omemo:1']]))[1]::text;
_encryption := (xpath('//jc:message/xo:encryption |//message/xo:encryption', NEW.value::xml , ARRAY[ARRAY['jc','jabber:client'],ARRAY['xo','urn:xmpp:eme:0']]))[1]::text;
_message_id := (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text;
_broadcast_title := (xpath('//message/xc:vncTalkBroadcast/@title', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1];
_origtarget := (xpath('//message/@origtarget | /message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text;
_x_invite := (xpath('//message/nsx:x/nsx:invite/@from'::text, new.value::xml, ARRAY[ARRAY['nsx'::text, 'http://jabber.org/protocol/muc#user'::text]]))[1]::text;
_x_broadcast := xpath('//message/xc:vncTalkBroadcast'::text, new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]);
_x_when := extract(epoch from now())::integer;
_from := (xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text;
_from_bare := split_part(_from, '/', 1);
_to := (xpath('/message/@to | /jc:message/@to', NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text;
_x_conference_scheduler := (xpath('/message/xc:vncTalkConferenceScheduler | /jc:message/xc:vncTalkConferenceScheduler', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text;
_x_role := (xpath('//message/mu:x/mu:item/@role', NEW.value::xml, ARRAY[ARRAY['mu','http://jabber.org/protocol/muc#user']]))[1]::text;
_x_affiliation := (xpath('//message/mu:x/mu:item/@affiliation', NEW.value::xml, ARRAY[ARRAY['mu','http://jabber.org/protocol/muc#user']]))[1]::text;
if NEW.store = 'muc_log' then
insert into processed_messages (sort_id, body, htmlbody, topic, parent0, parent, date, "from", "to", owner, room, type, id,
x_attachment, x_location, x_replaceMsgId, x_origMessage, x_vncConference, x_forwardMessage, group_action, mention, "encrypted", encryption)
select
NEW.sort_id as sort_id,
_body as body, _htmlbody as htmlbody, _topic as topic, _parent0 as parent0, _parent as parent,
extract(epoch from now())::integer as date,
room_nick_jid_map.user_jid as "from",
NEW.user||'@'||NEW.host as "to",
NEW.user||'@'||NEW.host as owner,
NEW.user||'@'||NEW.host as room,
'groupchat' as type,
(case
when _message_id IS NULL then NEW.key
else _message_id
end) as id,
_x_attachment as x_attachment, _x_location as x_location, _x_replaceMsgId as x_replaceMsgId, _original_message as original_message,
_x_conference as x_conference, _x_forwardMessage as x_forwardMessage, _group_action as group_action, _mention as mention,
_x_encrypted as "encrypted", _encryption as encryption
from room_nick_jid_map
where (room_nick_jid_map.nickname = _from);
if (_x_role = 'participant' and _x_affiliation = 'none') then
insert into processed_messages (sort_id, body, htmlbody, topic, parent0, parent, date, "from", "to", owner, room, type, id,
x_attachment, x_location, x_replaceMsgId, x_origMessage, x_vncConference, x_forwardMessage, group_action, mention, "encrypted", encryption)
select
NEW.sort_id as sort_id,
_body as body, _htmlbody as htmlbody, _topic as topic, _parent0 as parent0, _parent as parent,
extract(epoch from now())::integer as date,
split_part(_from, '/', 2) as "from",
NEW.user||'@'||NEW.host as "to",
NEW.user||'@'||NEW.host as owner,
NEW.user||'@'||NEW.host as room,
'groupchat' as type,
(case
when _message_id IS NULL then NEW.key
else _message_id
end) as id,
_x_attachment as x_attachment, _x_location as x_location, _x_replaceMsgId as x_replaceMsgId, _original_message as original_message,
_x_conference as x_conference, _x_forwardMessage as x_forwardMessage, _group_action as group_action, _mention as mention,
_x_encrypted as "encrypted", _encryption as encryption;
end if;
if (jsonb_array_length(_mention::jsonb) > 0) then
insert into totalmentions (username, target, key, type) select
split_part(split_part(jsonb_array_elements(_mention::jsonb)::text, 'xmpp:',2), '"',1) as username,
NEW.user || '@' || NEW.host as target,
NEW.key as key,
'groupchat' as type;
end if;
if NEW.value like '%ENDED_CALL</type></group_action%' then
_x_conferencekey := (xpath('//xc:vncTalkConference/xc:conferenceId/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
_x_conference_from := (xpath('//xc:vncTalkConference/xc:from/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
update recent_history_table
set has_active_call = false,
updated_at=_x_when,
last_callstate_update=_x_when
where target=NEW.user||'@'||NEW.host;
update call_tracking set
updated_at = _x_when,
state = 'ended'
where callid in (select callid from call_tracking where receipient=_x_conference_from and conferenceid=_x_conferencekey order by callid desc limit 1);
end if;
if NEW.value like '%<eventType>leave</eventType>%' then
_x_conferencekey := (xpath('//xc:vncTalkConference/xc:conferenceId/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
_x_conference_from := (xpath('//xc:vncTalkConference/xc:from/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
update call_tracking set
updated_at = _x_when,
state = 'leave'
where callid in (select callid from call_tracking where receipient=_x_conference_from and conferenceid=_x_conferencekey order by callid desc limit 1);
end if;
if NEW.value like '%<eventType>join</eventType>%' then
_x_conferencekey := (xpath('//xc:vncTalkConference/xc:conferenceId/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
_x_conference_from := (xpath('//xc:vncTalkConference/xc:from/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
update recent_history_table
set has_active_call = true,
updated_at=_x_when,
last_callstate_update=_x_when
where target=NEW.user||'@'||NEW.host;
update call_tracking set
updated_at = _x_when,
state = 'join'
where callid in (select callid from call_tracking where receipient=_x_conference_from and conferenceid=_x_conferencekey order by callid desc limit 1);
end if;
if _x_conference_scheduler is not null then
insert into recent_history_table (username, target, type, timestamp, message, message_id, original_message, x_attachment, x_conference, mentions, incoming, received_receipt, updated_at, deleted, has_data, x_conference_scheduler, x_conference_start) select
distinct on (room_membership.username) room_membership.username as username,
NEW.user || '@' || NEW.host as target,
'groupchat' as type,
extract(epoch from now()) as timestamp,
_body as message,
_message_id AS message_id,
_original_message as original_message,
_x_attachment as x_attachment, _x_conference as x_conference, _mention as mentions,
(case
when (_from = room_membership.user_room_nickname) then false
else true
end) as incoming,
false as received_receipt,
extract(epoch from now()) as updated_at,
((xpath('//group_action/type/text() | //xc:group_action/xc:type/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] is not null) as deleted,
true as has_data,
_x_conference_scheduler as x_conference_scheduler,
extract(epoch from to_timestamp(((xpath('/message/xc:vncTalkConferenceScheduler/xc:startTime/text() | /jc:message/xc:vncTalkConferenceScheduler/xc:startTime/text()', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text), 'YYYY-MM-DD HH24:MI:SS')) as x_conference_start
from room_membership where NEW.host=room_membership.host and NEW.user=room_membership.room
order by username, room_membership.user_room_nickname desc;
end if;
if (_body is not null) and (_body <> '') then
insert into recent_history_table (username, target, type, timestamp, message, message_id, original_message, x_attachment, x_conference, mentions, incoming, received_receipt, updated_at, deleted, has_data) select
distinct on (room_membership.username) room_membership.username as username,
NEW.user || '@' || NEW.host as target,
'groupchat' as type,
extract(epoch from now()) as timestamp,
_body as message, _message_id as message_id, _original_message as original_message, _x_attachment as x_attachment, _x_conference as x_conference,
_mention as mentions,
(case
when (_from = room_membership.user_room_nickname) then false
else true
end) as incoming,
false as received_receipt,
_x_when as updated_at,
(_group_action is not null) as deleted,
true as has_data
from room_membership where NEW.host=room_membership.host and NEW.user=room_membership.room
order by username, room_membership.user_room_nickname desc;
if (_x_conference is null) then
insert into read_conversation (username, target, timestamp) select
room_nick_jid_map.user_jid AS username,
(new."user" || '@'::text) || new.host AS target,
_x_when AS "timestamp"
FROM room_nick_jid_map
WHERE room_nick_jid_map.room_name = ((new."user" || '@'::text) || new.host) AND room_nick_jid_map.nickname = _from;
end if;
end if;
end if;
if NEW.store = 'muc_remote' then
insert into processed_messages (sort_id, body, htmlbody, topic, parent0, parent, date, "from", "to", owner, room, type, id,
x_attachment, x_location, x_replaceMsgId, x_origMessage, x_vncConference, x_forwardMessage, group_action, mention, "encrypted", encryption)
select
NEW.sort_id as sort_id,
_body as body, _htmlbody as htmlbody, _topic as topic, _parent0 as parent0, _parent as parent,
extract(epoch from now())::integer as date,
room_nick_jid_map.user_jid as "from",
NEW.user||'@'||NEW.host as "to",
NEW.user||'@'||NEW.host as owner,
NEW.with as room,
'groupchat' as type,
(case
when _message_id IS NULL then NEW.key
else _message_id
end) as id,
_x_attachment as x_attachment, _x_location as x_location, _x_replaceMsgId as _x_replaceMsgId, _original_message as original_message,
_x_conference as x_conference, _x_forwardMessage as x_forwardMessage, _group_action as group_action, _mention as mention,
_x_encrypted as "encrypted", _encryption as encryption
from room_nick_jid_map
where (room_nick_jid_map.nickname = _from);
-- mentions
if (jsonb_array_length(_mention::jsonb) > 0) then
insert into totalmentions (username, target, key, type) select
split_part(split_part(jsonb_array_elements(_mention::jsonb)::text, 'xmpp:',2), '"',1) as username,
NEW.user || '@' || NEW.host as target,
NEW.key as key,
'groupchat' as type;
end if;
if (_group_action = 'UPDATE_ENCRYPTION') then
if ((xpath('//group_action/data/text() | //xc:group_action/xc:data/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1]::text = 1) then
update recent_history_table set direct_e2e=true where target=NEW.with;
else
update recent_history_table set direct_e2e=false where target=NEW.with;
end if;
end if;
if NEW.value like '%ENDED_CALL</type></group_action%' then
update recent_history_table
set has_active_call = false,
updated_at=extract(epoch from now())::integer,
last_callstate_update=extract(epoch from now())::integer
where target=NEW.with;
end if;
if NEW.value like '%<eventType>join</eventType>%' then
update recent_history_table
set has_active_call = true,
updated_at=extract(epoch from now())::integer,
last_callstate_update=extract(epoch from now())::integer
where target=NEW.with;
end if;
if _x_conference_scheduler is not null then
update recent_history_table
set x_conference_scheduler=_x_conference_scheduler,
x_conference_start=extract(epoch from to_timestamp(((xpath('/message/xc:vncTalkConferenceScheduler/xc:startTime/text() | /jc:message/xc:vncTalkConferenceScheduler/xc:startTime/text()', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text), 'YYYY-MM-DD HH:MI:SS'))
where recent_history_table.target=NEW.with;
end if;
if (_body is not null) and (_body <> '') then
insert into recent_history_table (username, target, type, timestamp, message, message_id, original_message, x_attachment, x_conference, mentions, incoming, received_receipt, updated_at, deleted, has_data) select
NEW.user||'@'||NEW.host as username,
NEW.with as target,
'groupchat' as type,
extract(epoch from now()) as timestamp,
_body as message, _message_id as message_id, _original_message as original_message, _x_attachment as x_attachment, _x_conference as x_conference,
_mention as mentions,
(case
when (NEW.user||'@'||NEW.host = room_nick_jid_map.user_jid) then false
else true
end) as incoming,
false as received_receipt,
_x_when as updated_at,
(_group_action is not null) as deleted,
true as has_data
from room_nick_jid_map
where (room_nick_jid_map.nickname = _from);
end if;
end if;
if NEW.store = 'archive' then
-- processed_messages
if (NEW.with != NEW.user||'@'||NEW.host) then
insert into processed_messages (sort_id, body, htmlbody, date, "from", "to", "broadcast-sender", owner, type, id, broadcast,
x_attachment, x_location, x_replaceMsgId, x_origMessage, x_vncConference, x_forwardMessage, broadcast_title, group_action, "encrypted", encryption)
select
NEW.sort_id as sort_id,
_body as body, _htmlbody as htmlbody,
extract(epoch from now())::integer as date,
(case
when (_origtarget IS NULL) then _from_bare
else _origtarget
end) as from,
_to AS "to",
(case
when _origtarget IS NULL then
NULL
else _from_bare
end) as "broadcast-sender",
NEW.user||'@'||NEW.host as owner,
'chat' as type,
(case
when _message_id IS NULL then NEW.key
else _message_id
end) as id,
_origtarget as broadcast,
_x_attachment as x_attachment, _x_location as x_location, _x_replaceMsgId as x_replaceMsgId, _original_message as original_message,
_x_conference as x_conference, _x_forwardMessage as x_forwardMessage,
_broadcast_title as broadcast_title,
_group_action as group_action, _x_encrypted as "encrypted", _encryption as encryption
where
_x_invite IS NULL
;
if NEW.value like '%<eventType>invite</eventType>%' then
_x_conferencekey := (xpath('//xc:vncTalkConference/xc:conferenceId/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
insert into conferencemap (conferencekey, value)
select
_x_conferencekey as conferencekey,
'{"value":"'||(xpath('//xc:vncTalkConference/xc:jitsiRoom/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1]||'","jitsiurl":"'||(xpath('//xc:vncTalkConference/xc:jitsiURL/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1]||'"}' as value
;
if NEW.with = _to then
insert into call_tracking (callid, started_at, caller, conferenceId, receipient, state)
select
NEW.sort_id as callid,
_x_when as started_at,
_from_bare as caller,
_x_conferencekey as conferenceId,
_to as receipient,
'invite' as state
;
end if;
end if;
if NEW.value like '%<eventType>leave</eventType>%' then
_x_conferencekey := (xpath('//xc:vncTalkConference/xc:conferenceId/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
update call_tracking set
updated_at = _x_when,
state = 'leave'
where callid in (select callid from call_tracking where (receipient=_from_bare or caller=_from_bare) and conferenceid=_x_conferencekey order by callid desc limit 1);
end if;
if NEW.value like '%<eventType>join</eventType>%' then
_x_conferencekey := (xpath('//xc:vncTalkConference/xc:conferenceId/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1];
update call_tracking set
updated_at = _x_when,
state = 'join'
where callid in (select callid from call_tracking where receipient=_from_bare and conferenceid=_x_conferencekey order by callid desc limit 1);
end if;
if (NEW.with like '%@conference.%') then
-- init read conversation
insert into read_conversation (username, target, timestamp)
select
(split_part(_to, '/',1)) as username,
(split_part(_from, '/',1)) as target,
(extract(epoch from now())::integer - 60) as timestamp;
-- update room_nick_jid_map_inviter
insert into room_nick_jid_map (room_name, user_jid, nickname, since)
select
(split_part(_from, '/',1)) as room_name,
split_part(split_part(xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:x:conference']])::text, '/', 1), '"', 2) as user_jid,
((split_part(_from, '/',1))||'/'||split_part(split_part(xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:x:conference']])::text, '/', 1), '"', 2)) as nickname,
extract(epoch from now())::integer;
-- update room_nick_jid_map_invitee
insert into room_nick_jid_map (room_name, user_jid, nickname, since)
select
(split_part((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1)) as room_name,
(split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1)) as user_jid,
((split_part((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1))||'/'||(split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1))) as nickname,
extract(epoch from now())::integer as since;
else
if (_body is not null) and ((_x_conference is null) or (_x_conference like '%#%')) and (_x_invite is null) then
if (_origtarget is null) then
-- update chat recent
insert into recent_history_table (username, target, type, timestamp, message, message_id, original_message, x_attachment, x_conference, incoming, received_receipt, updated_at, has_data, deleted) select
NEW.user || '@' || NEW.host as username,
NEW.with as target,
'chat' as type,
_x_when as timestamp,
_body as message, _message_id as message_id, _original_message as original_message, _x_attachment as x_attachment, _x_conference as x_conference,
(case
when (split_part(_to, '/',1) = NEW.user || '@' || NEW.host) then true
else false
end) as incoming,
NULL as received_receipt,
_x_when as updated_at,
true as has_data,
false as deleted
order by target,timestamp desc;
end if;
if (_origtarget is not null) and (_x_broadcast is not null) then
insert into recent_history_table (username, target, type, timestamp, message, message_id, original_message, x_attachment, x_conference, broadcast_title, incoming, received_receipt, updated_at, has_data, deleted) select
NEW.user || '@' || NEW.host as username,
_origtarget as target, 'broadcast' as type, _x_when as timestamp,
_body as message, _message_id as message_id, _original_message as original_message, _x_attachment as x_attachment, _x_conference as x_conference,
_broadcast_title as broadcast_title,
(case
when (split_part(_to, '/',1) = NEW.user || '@' || NEW.host) then true
else false
end) as incoming,
NULL as received_receipt,
_x_when as updated_at,
true as has_data,
false as deleted
order by timestamp desc;
end if;
if (_origtarget is not null) and (_x_broadcast is not null) then
insert into broadcast_audience (broadcast_owner, broadcast_target, audience, title) select
NEW.with as broadcast_owner,
_origtarget as broadcast_target,
array_to_json(xpath('//message/xc:vncTalkBroadcast/xc:*/text()'::text, NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))::jsonb as audience,
_broadcast_title as title;
end if;
end if;
end if;
end if;
if (_to = NEW.with) and (_body is not null) and (_body <> '') then
insert into read_conversation (username, target, "timestamp") select
(new."user" || '@'::text) || new.host AS username,
NEW.with as target,
_x_when as timestamp;
end if;
end if;
if NEW.store = 'kick' then
delete from room_nick_jid_map where (room_name=NEW.user||'@'||NEW.host) and (user_jid=NEW.with);
update read_conversation set timestamp=_x_when + 30 where username=NEW.with and (target=NEW.user||'@'||NEW.host);
update recent_history_table set deleted = true where ((target=NEW.user||'@'||NEW.host) and (username = NEW.with));
end if;
if NEW.store = 'receipts' then
_received_id := (xpath('//received/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text;
update processed_messages set receipts = _x_when::text, updated = _x_when
where (processed_messages.id = _received_id)
and (processed_messages.owner = NEW.user||'@'||NEW.host) ;
update recent_history_table set received_receipt=true, updated_at = _x_when
where (recent_history_table.message_id = _received_id)
and (recent_history_table.username = NEW.user||'@'||NEW.host);
end if;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger process_messages after insert on prosodyarchive
for each row execute procedure fprocess_messages();
-- create or replace function fupdate_room_nick_jid_old()
create or replace function fupdate_room_nick_jid()
returns trigger AS
$BODY$
BEGIN
delete from room_nick_jid_map where (room_name=NEW.user||'@'||NEW.host) and (user_jid not in (select jsonb_object_keys(NEW.value::jsonb) as user_jid));
update recent_history_table set deleted = true where ((target=NEW.user||'@'||NEW.host) and username not in (select jsonb_object_keys(NEW.value::jsonb)));
insert into room_nick_jid_map (room_name, user_jid, nickname, since) select
NEW.user||'@'||NEW.host as room_name,
jsonb_object_keys(NEW.value::jsonb) as user_jid,
NEW.user||'@'||NEW.host||'/'||jsonb_object_keys(NEW.value::jsonb) as nickname,
(extract(epoch from now())::integer - 60) as since;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create or replace function fupdate_room_nick_jid()
returns trigger AS
$BODY$
BEGIN
delete from room_nick_jid_map where (room_name=NEW.user||'@'||NEW.host) and (user_jid not in (select jsonb_object_keys(NEW.value::jsonb) as user_jid));
update recent_history_table set deleted = true where ((target=NEW.user||'@'||NEW.host) and username not in (select jsonb_object_keys(NEW.value::jsonb)));
insert into room_nick_jid_map (room_name, user_jid, nickname, since) select
NEW.user||'@'||NEW.host as room_name,
jsonb_object_keys(NEW.value::jsonb) as user_jid,
NEW.user||'@'||NEW.host||'/'||jsonb_object_keys(NEW.value::jsonb) as nickname,
(extract(epoch from now())::integer - 60) as since;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger update_room_nick_jid after insert on prosody
for each row
when (NEW.key='_affiliations')
execute procedure fupdate_room_nick_jid();
create or replace function fupdate_mention_stamp()
returns trigger AS
$BODY$
BEGIN
update recent_history_table set last_mention_time=extract(epoch from now())::integer
where username=NEW.username and target=NEW.target;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create or replace function fupdate_room_nick_jid_remote()
returns trigger AS
$BODY$
BEGIN
insert into room_nick_jid_map (room_name, user_jid, nickname, since) select
split_part(NEW.key, '/',1) as room_name,
NEW.value as user_jid,
NEW.key as nickname,
(extract(epoch from now())::integer - 60) as since
where (NEW.key ilike '%/%');
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger update_room_nick_jid_remote after insert on prosody
for each row
when (NEW.store='muc_remote' and (NEW.key ilike '%/%'))
execute procedure fupdate_room_nick_jid_remote();
create trigger update_mention_stamp after insert on totalmentions
for each row execute procedure fupdate_mention_stamp();
create or replace function fupdate_recent_avatar()
returns trigger AS
$BODY$
BEGIN
update recent_history_table
set
last_avatar_update = NEW.value::integer,
updated_at=EXTRACT(EPOCH FROM now())::integer
where recent_history_table.target=NEW.user||'@'||new.host and NEW.store='avatarupdate';
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger update_recent_avatar after insert on prosody
for each row
when (NEW.store='avatarupdate')
execute procedure fupdate_recent_avatar();
create or replace function fupdate_iom_callstates()
returns trigger as
$BODY$
BEGIN
update recent_history_table
set has_active_call=true,
last_callstate_update = EXTRACT(EPOCH FROM now())::integer,
updated_at = EXTRACT(EPOCH FROM now())::integer
where recent_history_table.target = NEW.user and NEW.value='callactive';
update recent_history_table
set has_active_call=false,
last_callstate_update = EXTRACT(EPOCH FROM now())::integer,
updated_at = EXTRACT(EPOCH FROM now())::integer
where recent_history_table.target = NEW.user and NEW.value='callinactive';
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger update_iom_callstates after insert on prosody
for each row
when (NEW.store='callactive')
execute procedure fupdate_iom_callstates();
create or replace function fupdate_msg_reactions()
returns trigger AS
$BODY$
BEGIN
update processed_messages
set updated = EXTRACT(EPOCH FROM now())::integer,
reactions = r.v from (select jsonb_agg(jsonb_build_object(reactor, reaction)) as v from emojireactions where emojireactions.msgid=NEW.msgid) as r
where processed_messages.id=NEW.msgid;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger update_msg_reactions after insert or update on emojireactions
for each row execute procedure fupdate_msg_reactions();
create or replace function fupdate_msg_delreactions()
returns trigger AS
$BODY$
BEGIN
update processed_messages
set updated = EXTRACT(EPOCH FROM now())::integer,
reactions = r.v from (select jsonb_agg(jsonb_build_object(reactor, reaction)) as v from emojireactions where emojireactions.msgid=OLD.msgid) as r
where processed_messages.id=OLD.msgid;
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger update_msg_delreactions after delete on emojireactions
for each row execute procedure fupdate_msg_delreactions();
CREATE TABLE if not exists groupchat (
roomjid text NOT NULL,
roomtitle text,
PRIMARY KEY (roomjid)
);
CREATE or replace RULE upsert_groupchat AS
ON INSERT TO groupchat WHERE (EXISTS ( SELECT 1 FROM groupchat WHERE (roomjid = new.roomjid)))
DO INSTEAD
UPDATE groupchat SET roomtitle = new.roomtitle
WHERE (groupchat.roomjid = new.roomjid);
CREATE or replace FUNCTION fupdate_groupchat()
RETURNS trigger AS
$BODY$
BEGIN
if NEW.store = 'config' AND NEW.key='_data' then
insert into groupchat (roomjid, roomtitle) select
NEW.user||'@'||NEW.host as roomjid,
NEW.value::jsonb->>'subject' as roomtitle;
end if;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
create trigger update_groupchat AFTER INSERT ON prosody
FOR EACH ROW WHEN (new.key = '_data'::text AND new.store = 'config'::text)
EXECUTE PROCEDURE fupdate_groupchat();
@@ -0,0 +1,41 @@
{{- if .Values.dbMigration.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "prosody.fullname" . }}-db-post-upgrade
labels:
{{- include "prosody.labels" . | nindent 4 }}
annotations:
helm.sh/hook: post-install,post-upgrade
helm.sh/hook-weight: "5"
helm.sh/hook-delete-policy: hook-succeeded
spec:
backoffLimit: 0
template:
metadata:
labels:
{{- include "prosody.selectorLabels" . | nindent 8 }}
spec:
restartPolicy: Never
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: db-migration
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/bin/sh", "/vnc/config/migrate.sh", "post-upgrade"]
env:
{{- range $key, $value := $.Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- range $envName, $secretKey := $.Values.secretEnv.keys }}
- name: {{ $envName }}
valueFrom:
secretKeyRef:
name: {{ $.Values.secretEnv.existingSecret | quote }}
key: {{ $secretKey | quote }}
{{- end }}
{{- end }}
@@ -0,0 +1,41 @@
{{- if .Values.dbMigration.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "prosody.fullname" . }}-db-pre-upgrade
labels:
{{- include "prosody.labels" . | nindent 4 }}
annotations:
helm.sh/hook: pre-upgrade
helm.sh/hook-weight: "-5"
helm.sh/hook-delete-policy: hook-succeeded
spec:
backoffLimit: 0
template:
metadata:
labels:
{{- include "prosody.selectorLabels" . | nindent 8 }}
spec:
restartPolicy: Never
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: db-migration
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/bin/sh", "/vnc/config/migrate.sh", "pre-upgrade"]
env:
{{- range $key, $value := $.Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- range $envName, $secretKey := $.Values.secretEnv.keys }}
- name: {{ $envName }}
valueFrom:
secretKeyRef:
name: {{ $.Values.secretEnv.existingSecret | quote }}
key: {{ $secretKey | quote }}
{{- end }}
{{- end }}
+6
View File
@@ -40,6 +40,12 @@ tlsUpdates:
enabled: false enabled: false
secretName: none secretName: none
# Pre-upgrade and post-upgrade Helm hook Jobs that detect the database
# state and run the appropriate SQL migrations from db-customization/.
# The hooks use the same image and DB credentials as the Prosody container.
dbMigration:
enabled: true
service: service:
type: NodePort type: NodePort
port: 5280 port: 5280