Files
vnctalk-prosody/db-customization/prosody-queries-noowner.sql
T
Stefan-Sanger e1c78e7358 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>
2026-07-15 17:59:09 +02:00

1347 lines
67 KiB
SQL

-- database schema
create extension if not exists pgcrypto;
-- comment to test upgrade
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 table if not exists recent_history_table (
username text,
target text,
type text,
timestamp integer,
message text,
original_message text,
x_attachment text,
x_conference text,
incoming boolean,
PRIMARY KEY (target, username)
);
alter table recent_history_table add column message_id text;
alter table recent_history_table add column received_receipt boolean;
alter table recent_history_table add column updated_at integer;
alter table recent_history_table add column mute_sound integer;
alter table recent_history_table add column mute_notification integer;
alter table recent_history_table add column has_data boolean;
alter table recent_history_table add column broadcast_title text;
alter table recent_history_table add column mentions text;
alter table recent_history_table add column sort_id bigint;
alter table recent_history_table add column deleted boolean;
alter table recent_history_table add column pad_read integer;
alter table recent_history_table add column has_pads boolean;
alter table recent_history_table add column last_mention_time integer;
alter table recent_history_table add column last_avatar_update integer;
alter table recent_history_table add column direct_e2e boolean;
alter table recent_history_table add column has_active_call boolean;
alter table recent_history_table add column last_callstate_update integer;
alter table recent_history_table add column x_conference_start integer;
alter table recent_history_table add column retention_time integer;
alter table recent_history_table add column x_conference_scheduler text;
alter table recent_history_table add column is_favourite boolean;
alter table recent_history_table add column is_pinned boolean;
alter table recent_history_table add column audience_only boolean;
alter table recent_history_table add column has_iom boolean;
alter table recent_history_table add column pin_order integer;
update recent_history_table set updated_at=0 where updated_at is NULL;
update recent_history_table set mute_notification=0 where mute_notification is NULL;
update recent_history_table set mute_sound=0 where mute_sound is NULL;
update recent_history_table set has_data=true where has_data is NULL;
create table if not exists archive_inactive_table (
username text,
target text,
timestamp integer,
content text,
PRIMARY KEY (target, username)
);
create or replace rule insert_into_archive_inactive as
on insert to archive_inactive_table where (exists (select 1 from archive_inactive_table where username=NEW.username and target=NEW.target))
do instead
update archive_inactive_table set timestamp=NEW.timestamp,
content=NEW.content
where target=NEW.target and username=NEW.username;
create or replace rule update_recent_archive as
on update to archive_inactive_table
do update recent_history_table set updated_at=extract(epoch from now())::integer
where target=NEW.target and username=NEW.username;
create table if not exists broadcast_audience (
broadcast_owner text,
broadcast_target text,
audience jsonb,
title text,
PRIMARY KEY (broadcast_owner, broadcast_target)
);
alter table broadcast_audience add column tags jsonb;
alter table broadcast_audience add column description text;
create or replace rule upsert_broadcast_audience as
on insert to broadcast_audience where (exists (select 1 from broadcast_audience where broadcast_target=NEW.broadcast_target))
do instead
update broadcast_audience set
audience=NEW.audience,
title=NEW.title,
tags=NEW.tags,
description=NEW.description
where broadcast_target=NEW.broadcast_target;
create table if not exists group_owners (
room text,
owner text,
primary key (room)
);
alter table group_owners add column created integer;
alter table group_owners add column updated integer;
create or replace rule upsert_group_owners as
on insert to group_owners where (exists (select 1 from group_owners where room=NEW.room))
do instead
update group_owners set
owner=NEW.owner
where room=NEW.room;
create table if not exists group_avatarids (
room text,
avatarid text,
primary key (room)
);
create or replace rule upsert_group_avatarids as
on insert to group_avatarids where (exists (select 1 from group_avatarids where room=NEW.room))
do instead
update group_avatarids set
avatarid=NEW.avatarid
where room=NEW.room;
create or replace rule cache_group_avatarids as
on insert to prosody where NEW.store='vcard_muc' and NEW.key='' and NEW.type='json' and NEW.host like 'conference.%'
do insert into group_avatarids (room, avatarid) select
NEW.user||'@'||NEW.host as room,
encode(digest(decode(split_part(split_part(split_part(NEW.value, 'BINVAL', 2), '__array":["', 2),'"',1), 'base64'), 'sha1'), 'hex') as groupavarid;
create table if not exists conferenceMapping (
conferenceKey text,
value text,
primary key (conferenceKey)
);
create or replace rule upsert_confmap as
on insert to conferenceMapping where (exists (select 1 from conferenceMapping where conferenceKey=NEW.conferenceKey))
do instead update conferenceMapping
set value=NEW.value
where conferenceKey=NEW.conferenceKey;
create table if not exists conferenceMap (
conferenceKey text,
value text,
primary key (conferenceKey)
);
create or replace rule upsert_conferencemap as
on insert to conferenceMap where (exists (select 1 from conferenceMap where conferenceKey=NEW.conferenceKey))
do instead update conferenceMap
set value=NEW.value
where conferenceKey=NEW.conferenceKey;
create table if not exists exclude_from_history (
username text,
target text,
timestamp integer,
PRIMARY KEY (target, username)
);
create or replace rule insert_into_exclude_from_history as
on insert to exclude_from_history where (exists (select 1 from exclude_from_history where username=NEW.username and target=NEW.target))
do instead
update exclude_from_history set timestamp=NEW.timestamp
where username=NEW.username and target=NEW.target;
create table if not exists read_conversation (
username text,
target text,
timestamp integer,
PRIMARY KEY (target, username)
);
create or replace rule insert_into_read_conversation as
on insert to read_conversation where (exists (select 1 from read_conversation where username=NEW.username and target=NEW.target))
do instead
update read_conversation set timestamp=NEW.timestamp
where target=NEW.target and username=NEW.username;
create table if not exists read_pad (
username text,
target text,
timestamp integer,
PRIMARY KEY (target, username)
);
create or replace rule insert_into_read_pad as
on insert to read_pad where (exists (select 1 from read_pad where username=NEW.username and target=NEW.target))
do instead
update read_pad set timestamp=NEW.timestamp
where target=NEW.target and username=NEW.username;
create table if not exists external_email_invites (
username text,
meeting text,
email text,
timestamp integer,
PRIMARY KEY (username, meeting, email)
);
create or replace rule inser_external_email_invites as
on insert to external_email_invites where (exists (select 1 from external_email_invites where username=NEW.username and meeting=NEW.meeting and email=NEW.meeting))
do instead nothing;
create or replace rule update_read_conversation as
on update to read_conversation do
update recent_history_table set
updated_at=EXTRACT(EPOCH FROM now())::integer
where recent_history_table.username=NEW.username and recent_history_table.target=NEW.target;
create or replace rule update_read_pad as
on update to read_pad do
update recent_history_table set
pad_read=EXTRACT(EPOCH FROM now())::integer
where recent_history_table.username=NEW.username and recent_history_table.target=NEW.target;
drop table if exists total_mentions cascade;
create table if not exists totalmentions (
username text,
target text,
key text,
type text,
PRIMARY KEY (target, username, key)
);
create rule upsert_totalmentions as
on insert to totalmentions where (exists (select 1 from totalmentions where username=NEW.username and target=NEW.target and key=NEW.key))
do instead nothing;
create or replace view total_mentions as
select
username,
target,
'groupchat' as type,
count(*) as total
from totalmentions group by username, target;
-- rule to do update instead of insert when record exists
create or replace rule instert_into_chat_recent as
on insert to recent_history_table where ((exists (select 1 from recent_history_table where target=NEW.target and username=NEW.username)) and NEW.timestamp is not null)
do instead
update recent_history_table set
type=NEW.type,
message=NEW.message,
original_message=NEW.original_message,
x_attachment=NEW.x_attachment,
x_conference=NEW.x_conference,
timestamp=NEW.timestamp,
incoming=NEW.incoming,
message_id=NEW.message_id,
received_receipt=null,
updated_at=NEW.timestamp,
has_data=true,
broadcast_title=NEW.broadcast_title,
sort_id=NEW.sort_id,
deleted=false,
mentions=NEW.mentions
where target=NEW.target and username=NEW.username and ((recent_history_table.deleted = false) or (NEW.deleted IS NOT NULL));
-- where target=NEW.target and username=NEW.username;
-- rule to do update instead of insert when record exists
create or replace rule instert_into_chat_recent_mute as
on insert to recent_history_table where ((exists (select 1 from recent_history_table where target=NEW.target and username=NEW.username)) and (NEW.timestamp is null) and (NEW.mute_notification is null))
do instead
update recent_history_table set
mute_sound = NEW.mute_sound,
updated_at=EXTRACT(EPOCH FROM now())::integer
where target=NEW.target and username=NEW.username;
create or replace rule instert_into_chat_recent_notify as
on insert to recent_history_table where ((exists (select 1 from recent_history_table where target=NEW.target and username=NEW.username)) and (NEW.timestamp is null) and (NEW.mute_sound is null))
do instead
update recent_history_table set
mute_notification = NEW.mute_notification,
updated_at=EXTRACT(EPOCH FROM now())::integer
where target=NEW.target and username=NEW.username;
-- create or replace rule update_muc_mention as
-- on update to recent_history_table where (NEW.type = 'groupchat' AND (NEW.mentions ilike '%xmpp:'||NEW.username||'%'))
-- do insert into total_mentions (username, target, type, total) select
-- NEW.username as username,
-- NEW.target as target,
-- 'groupchat' as type,
-- 0 as total;
create or replace rule update_muc_mention as
on insert to prosodyarchive where NEW.store = 'muc_log'
do insert into totalmentions (username, target, key, type) select
split_part(split_part(jsonb_array_elements(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']])))::text, 'xmpp:',2), '"',1) as username,
NEW.user || '@' || NEW.host as target,
NEW.key as key,
'groupchat' as type;
-- muc remote mentions
create or replace rule update_muc_remote_mention as
on insert to prosodyarchive where NEW.store = 'muc_remote'
do insert into totalmentions (username, target, key, type) select
split_part(split_part(jsonb_array_elements(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']])))::text, 'xmpp:',2), '"',1) as username,
NEW.with as target,
NEW.key as key,
'groupchat' as type;
create table if not exists room_nick_jid_map (
room_name text,
user_jid text,
nickname text,
primary key (room_name, user_jid, nickname)
);
alter table room_nick_jid_map add column since integer default 0;
create or replace rule
insert_room_nick_jid_map as
on insert to room_nick_jid_map where (exists ( select 1 from room_nick_jid_map where room_name=NEW.room_name and user_jid=NEW.user_jid and nickname=NEW.nickname))
do instead nothing;
-- check
-- update room_nick_jid_map
-- set since = NEW.since
-- where room_name=NEW.room_name and user_jid=NEW.user_jid and nickname=NEW.nickname;
-- do instead nothing;
create or replace view room_nicknames as
select
test.value as username,
test.key as user_room_nickname,
prosody.host as host,
prosody.user as room,
prosody.user || '@' || prosody.host as room_name
from prosody, jsonb_each_text(prosody.value::jsonb) as test
where
prosody.key='_occupants' ;
create or replace rule update_group_owners as
on insert to prosody where NEW.key='_affiliations' and (NEW.host like 'conference.%') and NEW.store='config' and (NEW.value ilike '%":"owner"%')
do insert into group_owners (room, owner)
select NEW.user||'@'||NEW.host as room,
reverse(split_part(reverse(split_part(NEW.value, '":"owner"',1)),'"',1)) as owner;
-- to remove I suppose!
create or replace rule update_room_nick_jid_map as
on insert to prosodyarchive where NEW.store = 'muc_log'
do insert into room_nick_jid_map (nickname, user_jid, room_name, since)
select
distinct on (room_nicknames.user_room_nickname) room_nicknames.user_room_nickname as nickname,
room_nicknames.username as user_jid,
room_nicknames.room_name as room_name,
extract(epoch from now())::integer as since
from room_nicknames where room_nicknames.host=NEW.host and room_nicknames.room=NEW.user
on conflict do nothing;
-- update room_nick_jid from invite messages
create or replace rule update_room_nick_jid_map_invitee as
on insert to prosodyarchive where NEW.store = 'archive' and (NEW.with like '%@conference.%')
do 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,
(case
when ((xpath('/message/x:x/x:invite/x:reason/text() | /jc:message/x:x/x:invite/x:reason/text()', NEW.value::xml, ARRAY[ARRAY['x', 'http://jabber.org/protocol/muc#user'], ARRAY['jc', 'jabber:client']]))[1]::text = '1') then 0
else extract(epoch from now())::integer
end) as since;
-- extract(epoch from now())::integer as since;
create or replace rule update_room_nick_jid_map_inviter as
on insert to prosodyarchive where NEW.store = 'archive' and (NEW.with like '%@conference.%')
do 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(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((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',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;
create or replace rule update_room_nick_jid_map_remote_inv as
on insert to prosodyarchive where NEW.store = 'muc_remote_inv'
do insert into room_nick_jid_map (room_name, user_jid, nickname, since)
select
NEW.user||'@'||NEW.host as room_name,
NEW.with as user_jid,
NEW.user||'@'||NEW.host||'/'||NEW.with as nickname,
extract(epoch from now())::integer;
create or replace rule init_read_conversation_invitee as
on insert to prosodyarchive where NEW.store = 'archive' and (NEW.with like '%@conference.%')
do insert into read_conversation (username, target, timestamp)
select
(split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1)) as username,
(split_part((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1)) as target,
(extract(epoch from now())::integer - 60) as timestamp;
-- end updtae
create or replace rule update_room_nick_jid_map_remote as
on insert to prosody where NEW.store='muc_remote' and (NEW.key like '%/%') and (NEW.value like '%@%')
do 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;
create or replace view room_membership as
select
jsonb_object_keys(prosody.value::jsonb) as username,
''::text as user_room_nickname,
prosody.host as host,
prosody.user as room
from prosody
where prosody.key='_affiliations';
-- rule to extract new singlechat message from prosodyarchive
create or replace rule update_single_chat_recent as
on insert to prosodyarchive where NEW.store = 'archive'
do 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,
NEW.when as timestamp,
(xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1] as message,
(xpath('/message/@id | /jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS message_id,
(xpath('//originalMessage | //xc:originalMessage', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as original_message,
(xpath('//attachment | //xc:attachment', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_attachment,
(xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_conference,
(case
when (split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) = NEW.user || '@' || NEW.host) then true
else false
end) as incoming,
NULL as received_receipt,
NEW.when as updated_at,
true as has_data,
false as deleted
where
NEW.store='archive'
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (xpath('//message/nsx:x/nsx:invite/@from'::text, new.value::xml, ARRAY[ARRAY['nsx'::text, 'http://jabber.org/protocol/muc#user'::text]]))[1]::text IS NULL
and (xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text IS NULL
and ( (
(xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] IS NOT NULL AND
(xpath('//vncTalkConference/conferenceId/text() | //xc:vncTalkConference/xc:conferenceId/text()', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1]::text ilike '%#%'
) or (xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] IS NULL )
AND NOT (NEW.with like '%@conference.%')
AND NOT (NEW.with = NEW.user || '@' || NEW.host)
order by target,timestamp desc;
-- rule to add broadcast
create or replace rule update_broadcast_recent as
on insert to prosodyarchive where NEW.store = 'archive'
do 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,
(xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text as target,
'broadcast' as type,
NEW.when as timestamp,
(xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1] as message,
(xpath('/message/@id | /jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS message_id,
(xpath('//originalMessage | //xc:originalMessage', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as original_message,
(xpath('//attachment | //xc:attachment', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_attachment,
(xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_conference,
(xpath('//message/xc:vncTalkBroadcast/@title', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1] as broadcast_title,
(case
when (split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) = NEW.user || '@' || NEW.host) then true
else false
end) as incoming,
NULL as received_receipt,
NEW.when as updated_at,
true as has_data,
false as deleted
where
NEW.store='archive'
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (xpath('//message/nsx:x/nsx:invite/@from'::text, new.value::xml, ARRAY[ARRAY['nsx'::text, 'http://jabber.org/protocol/muc#user'::text]]))[1]::text IS NULL
and (xpath('//message/xc:vncTalkBroadcast'::text, new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']])) IS NOT NULL
and (xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text IS NOT NULL
AND NOT (NEW.with like '%@conference.%')
order by timestamp desc;
-- rule to update broadcast audience list
create or replace rule update_broadcast_audience as
on insert to prosodyarchive where NEW.store = 'archive'
do insert into broadcast_audience (broadcast_owner, broadcast_target, audience, title, tags, description) select
NEW.with as broadcast_owner,
(xpath('//message/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1] as broadcast_target,
array_to_json(xpath('//message/xc:vncTalkBroadcast/xc:to/text()'::text, NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))::jsonb as audience,
(xpath('//message/xc:vncTalkBroadcast/@title', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text as title,
array_to_json(xpath('//message/xc:vncTalkBroadcast/xc:tag/text()'::text, NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))::jsonb as tags,
(xpath('//message/xc:vncTalkBroadcast/@description', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text as description
where
NEW.store='archive'
AND (xpath('//message/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1] IS NOT NULL
and (xpath('//message/xc:vncTalkBroadcast'::text, new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']])) IS NOT NULL
;
create or replace rule update_broadcast_audience_remote as
on insert to prosodyarchive where NEW.store = 'archive'
do insert into broadcast_audience (broadcast_owner, broadcast_target, audience, title) select
NEW.with as broadcast_owner,
(xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1] as broadcast_target,
'[]' as audience,
(xpath('//message/xc:vncTalkBroadcast/@title', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text as title
WHERE
NEW.store='archive'
AND (xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1] IS NOT NULL
and (xpath('//message/xc:vncTalkBroadcast'::text, NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']])) IS NOT NULL
and (split_part(NEW.with, '@', 2) <> NEW.host);
-- rule to extract new groupchat message from prosodyarchive
create or replace rule update_muc_recent as
on insert to prosodyarchive where NEW.store = 'muc_log'
do 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,
NEW.when as timestamp,
(xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1] as message,
(xpath('/message/@id | /jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS message_id,
(xpath('//originalMessage | //xc:originalMessage', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as original_message,
(xpath('//attachment | //xc:attachment', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_attachment,
(xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_conference,
(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']])))::text as mentions,
(case
when ((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text = room_membership.user_room_nickname) then false
else true
end) as incoming,
false as received_receipt,
NEW.when 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
from room_membership where NEW.host=room_membership.host and NEW.user=room_membership.room and NEW.store='muc_log'
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
order by username, room_membership.user_room_nickname desc;
create or replace rule update_muc_remote_recent as
on insert to prosodyarchive where NEW.store = 'muc_remote'
do insert into recent_history_table (username, target, type, timestamp, message, message_id, original_message, x_attachment, x_conference, mentions, incoming, received_receipt, updated_at, has_data) select
NEW.user || '@' || NEW.host as username,
NEW.with as target,
'groupchat' as type,
NEW.when as timestamp,
(xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1] as message,
(xpath('/message/@id | /jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS message_id,
(xpath('//originalMessage | //xc:originalMessage', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as original_message,
(xpath('//attachment | //xc:attachment', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_attachment,
(xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1] as x_conference,
(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']])))::text as mentions,
(case
when ((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text = room_nick_jid_map.nickname) then false
else true
end) as incoming,
false as received_receipt,
NEW.when as updated_at,
true as has_data
from room_nick_jid_map where NEW.with=room_nick_jid_map.room_name and (room_nick_jid_map.user_jid = (NEW.user || '@' || NEW.host)) and NEW.store='muc_remote'
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
order by username, room_nick_jid_map.nickname desc;
-- add direct_e2e field here
-- drop this
drop rule if exists update_receipt_recent on prosodyarchive cascade;
-- rule to mark conversation read on new singlechat message from prosodyarchive
create or replace rule mark_single_chat_read as
on insert to prosodyarchive where NEW.store = 'archive'
AND split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) = NEW.with
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
do insert into read_conversation (username, target, "timestamp")
SELECT
(new."user" || '@'::text) || new.host AS username,
NEW.with as target,
NEW.when as timestamp
where split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) = NEW.with;
-- rule to extract new groupchat message from prosodyarchive
create or replace rule mark_muc_read as
on insert to prosodyarchive where NEW.store = 'muc_log'
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (xpath('//vncTalkConference | //xc:vncTalkConference', NEW.value::xml, ARRAY[ARRAY['xc','xmpp:vnctalk']]))[1]::text IS NULL
do insert into read_conversation (username, target, timestamp) select
room_nick_jid_map.user_jid AS username,
(new."user" || '@'::text) || new.host AS target,
new."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 = (xpath('//message/@from | //jc:message/@from'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text
;
create table if not exists unread_message_ids (
message_id text,
sender text,
receipient text,
timestamp integer,
primary key ( message_id, sender, receipient)
);
create or replace rule
upsert_unread_message_ids as
on insert to unread_message_ids where (exists ( select 1 from unread_message_ids where message_id=NEW.message_id and sender=NEW.sender and receipient=NEW.receipient))
do instead nothing;
create or replace rule insert_unread_message_ids_chat as
on insert to prosodyarchive where NEW.store = 'archive'
do insert into unread_message_ids (message_id, sender, receipient, timestamp)
select
(case
when (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text IS NULL then NEW.key
else (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text
end) as message_id,
NEW.with as sender,
split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) as receipient,
NEW.when as timestamp
where split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) <> NEW.with
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (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 IS NULL
AND (xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text IS NULL
and (xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text IS NULL
AND NOT (NEW.with like '%@conference.%')
;
create or replace rule insert_unread_message_ids_broadcast as
on insert to prosodyarchive where NEW.store = 'archive'
do insert into unread_message_ids (message_id, sender, receipient, timestamp)
select
(case
when (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text IS NULL then NEW.key
else (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text
end) as message_id,
(xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text as receipient,
split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) as receipient,
NEW.when as timestamp
where split_part((xpath('//message/@to | //jabberclient:message/@to', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',1) <> NEW.with
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (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 IS NULL
AND (xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text IS NULL
and (xpath('//message/xc:vncTalkBroadcast'::text, new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']])) IS NOT NULL
and (xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text IS NOT NULL
AND NOT (NEW.with like '%@conference.%')
;
create or replace rule remove_unread_message_ids_deleted_chat as
on insert to prosodyarchive where NEW.store = 'archive' and (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 IS NOT NULL
do delete from unread_message_ids where message_id=(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;
create or replace rule insert_unread_message_ids_muc as
on insert to prosodyarchive where NEW.store = 'muc_log'
do insert into unread_message_ids (receipient, message_id, sender, timestamp)
select
DISTINCT room_membership.username AS receipient,
(case
when (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text IS NULL then NEW.key
else (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text
end) as message_id,
NEW.user || '@' || NEW.host as sender,
NEW.when::integer as timestamp
from room_membership
where room_membership.host=NEW.host and room_membership.room=NEW.user and room_membership.username not
in (select username from room_membership where user_room_nickname=((xpath('//message/@from | //jc:message/@from'::text, NEW.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text))
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (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 IS NULL
AND (xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text IS NULL
;
create or replace rule remove_unread_message_ids_deleted_muc as
on insert to prosodyarchive where NEW.store = 'muc_log' and (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 IS NOT NULL
do delete from unread_message_ids where message_id=(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;
create or replace rule insert_unread_message_ids_remotemuc as
on insert to prosodyarchive where NEW.store = 'muc_remote'
do insert into unread_message_ids (receipient, message_id, sender, timestamp)
select
NEW.user || '@' || NEW.host as receipient,
NEW.key as message_id,
NEW.with as sender,
NEW.when as timestamp
where (split_part((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',2) <> NEW.user || '@' || NEW.host)
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (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 IS NULL
AND (xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text IS NULL
and (xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text IS NULL
AND (NEW.with like '%@conference.%')
;
create or replace rule remove_unread_message_ids_deleted_remotemuc as
on insert to prosodyarchive where NEW.store = 'muc_remote' and (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 IS NOT NULL
do delete from unread_message_ids where message_id=(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;
create or replace view unread_conversation_ids as
select
read_conversation.username as username,
read_conversation.target as target,
jsonb_agg(unread_message_idjoin.message_id) as unreadids
from read_conversation
join unread_message_ids as unread_message_idjoin on
( unread_message_idjoin.receipient=read_conversation.username and unread_message_idjoin.sender=read_conversation.target and unread_message_idjoin.timestamp>read_conversation.timestamp)
group by read_conversation.username, read_conversation.target
union
select
unread_message_ids.receipient as username,
unread_message_ids.sender as target,
jsonb_agg(unread_message_ids.message_id) as unreadids
from unread_message_ids
where unread_message_ids.receipient||'#'||unread_message_ids.sender not in (select username||'#'||target from read_conversation)
group by unread_message_ids.receipient, unread_message_ids.sender
;
create table if not exists mute_conversation (
username text,
target text,
PRIMARY KEY (target, username)
);
create or replace rule
insert_mute_conversation as
on insert to mute_conversation where (exists ( select 1 from mute_conversation where username=NEW.username and target=NEW.target))
do instead nothing;
create or replace rule
update_mute_conversation as
on update to mute_conversation
do update recent_history_table
set updated_at=EXTRACT(EPOCH FROM now())::integer
where recent_history_table.username=NEW.username and recent_history_table.target=NEW.target;
create table if not exists mute_notification (
username text,
target text,
type integer,
PRIMARY KEY (target, username)
);
create or replace rule upsert_mute_notification as
on insert to mute_notification where (exists ( select 1 from mute_notification where username=NEW.username and target=NEW.target))
do instead update mute_notification
set type=NEW.type where username=NEW.username and target=NEW.target;
create or replace rule update_mute_notification as
on update to mute_notification do update recent_history_table
set updated_at=EXTRACT(EPOCH FROM now())::integer
where recent_history_table.username=NEW.username and recent_history_table.target=NEW.target;
create or replace rule insert_mute_notification as
on insert to mute_notification do update recent_history_table
set updated_at=EXTRACT(EPOCH FROM now())::integer
where recent_history_table.username=NEW.username and recent_history_table.target=NEW.target;
create or replace view unread_conversation_counts as
select
read_conversation.username as username,
read_conversation.target as target,
count(unread_message_idjoin.message_id) as unreadidcount
from read_conversation
join unread_message_ids as unread_message_idjoin on
( unread_message_idjoin.receipient=read_conversation.username and unread_message_idjoin.sender=read_conversation.target and unread_message_idjoin.timestamp>read_conversation.timestamp)
group by read_conversation.username, read_conversation.target
union
select
unread_message_ids.receipient as username,
unread_message_ids.sender as target,
count(unread_message_ids.message_id) as unreadidcount
from unread_message_ids
where unread_message_ids.receipient||'#'||unread_message_ids.sender not in (select username||'#'||target from read_conversation)
group by unread_message_ids.receipient, unread_message_ids.sender
;
create table profile_update_queue (
username text,
timestamp integer,
PRIMARY KEY (username)
);
create or replace rule upsert_profile_update_queue as
on insert to profile_update_queue where (exists (select 1 from profile_update_queue where username=NEW.username))
do instead update profile_update_queue set
timestamp=NEW.timestamp
where username=NEW.username;
create or replace rule update_profile_queue_from_insert as
on insert to prosody where (NEW.store='vcard' and NEW.type='json' and NEW.key='')
do insert into profile_update_queue (username, timestamp) select
NEW.user || '@' || NEW.host as username,
extract(epoch from now())::integer as timestamp;
create or replace rule update_profile_queue_from_update as
on update to prosody where (NEW.store='vcard' and NEW.type='json' and NEW.key='')
do insert into profile_update_queue (username, timestamp) select
NEW.user || '@' || NEW.host as username,
extract(epoch from now())::integer as timestamp;
create table if not exists unread_message_mention_ids (
message_id text,
sender text,
receipient text,
timestamp integer,
primary key ( message_id, sender, receipient)
);
create or replace rule
upsert_unread_message_mention_ids as
on insert to unread_message_mention_ids where (exists ( select 1 from unread_message_mention_ids where message_id=NEW.message_id and sender=NEW.sender and receipient=NEW.receipient))
do instead nothing;
create or replace rule insert_unread_mention_ids_muc as
on insert to prosodyarchive where NEW.store = 'muc_log'
do insert into unread_message_mention_ids (receipient, message_id, sender, timestamp)
select
DISTINCT room_membership.username AS receipient,
(case
when (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text IS NULL then NEW.key
else (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text
end) as message_id,
NEW.user || '@' || NEW.host as sender,
NEW.when::integer as timestamp
from room_membership
where room_membership.host=NEW.host and room_membership.room=NEW.user and room_membership.username not
in (select username from room_membership where user_room_nickname=((xpath('//message/@from | //jc:message/@from'::text, NEW.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text))
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (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 IS NULL
AND (xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text IS NULL
AND (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']])))::text like '%xmpp:'||room_membership.username||'%'
;
-- ToDo: insert_unread_mention_ids_muc_remote
create or replace rule insert_unread_message_ids_remotemuc as
on insert to prosodyarchive where NEW.store = 'muc_remote'
do insert into unread_message_mention_ids (receipient, message_id, sender, timestamp)
select
NEW.user || '@' || NEW.host as receipient,
(case
when (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text IS NULL then NEW.key
else (xpath('//message/@id | //jc:message/@id', NEW.value::xml, ARRAY[ARRAY['jc','jabber:client']]))[1]::text
end) as message_id,
NEW.with as sender,
NEW.when as timestamp
where (split_part((xpath('//message/@from | //jabberclient:message/@from', NEW.value::xml, ARRAY[ARRAY['jabberclient','jabber:client']]))[1]::text, '/',2) <> NEW.user || '@' || NEW.host)
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, new.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
AND (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 IS NULL
AND (xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', new.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text IS NULL
and (xpath('//message/xc:vncTalkBroadcast/@origtarget', NEW.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text IS NULL
AND (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']])))::text like '%xmpp:'||NEW.user||'@'||NEW.host||'%'
AND (NEW.with like '%@conference.%')
;
create or replace view unread_mention_ids as
select
read_conversation.username as username,
read_conversation.target as target,
jsonb_agg(unread_message_mention_idjoin.message_id) as unreadids
from read_conversation
join unread_message_mention_ids as unread_message_mention_idjoin on
( unread_message_mention_idjoin.receipient=read_conversation.username and unread_message_mention_idjoin.sender=read_conversation.target and unread_message_mention_idjoin.timestamp>read_conversation.timestamp)
group by read_conversation.username, read_conversation.target
union
select
unread_message_mention_ids.receipient as username,
unread_message_mention_ids.sender as target,
jsonb_agg(unread_message_mention_ids.message_id) as unreadids
from unread_message_mention_ids
where unread_message_mention_ids.receipient||'#'||unread_message_mention_ids.sender not in (select username||'#'||target from read_conversation)
group by unread_message_mention_ids.receipient, unread_message_mention_ids.sender
;
-- some required migrations to fill data...
insert into totalmentions (username, target, key, type)
select
split_part(split_part(jsonb_array_elements(to_jsonb(xpath('/message/xr:reference/@uri | /jc:message/xr:reference/@uri', prosodyarchive.value::xml, ARRAY[ARRAY['xr', 'urn:xmpp:reference:0'], ARRAY['jc', 'jabber:client']])))::text, 'xmpp:',2), '"',1) as username,
prosodyarchive.user||'@'||prosodyarchive.host as target,
prosodyarchive.key as key,
'groupchat' as type
from prosodyarchive
where (prosodyarchive.value like '%xmpp:%') and (prosodyarchive.value like '%reference type=''mention'' uri=''xmpp%') and prosodyarchive.store='muc_log';
insert into totalmentions (username, target, key, type)
select
split_part(split_part(jsonb_array_elements(to_jsonb(xpath('/message/xr:reference/@uri | /jc:message/xr:reference/@uri', prosodyarchive.value::xml, ARRAY[ARRAY['xr', 'urn:xmpp:reference:0'], ARRAY['jc', 'jabber:client']])))::text, 'xmpp:',2), '"',1) as username,
prosodyarchive.with as target,
prosodyarchive.key as key,
'groupchat' as type
from prosodyarchive
where (prosodyarchive.value like '%xmpp:%') and (prosodyarchive.value like '%reference type=''mention'' uri=''xmpp%') and prosodyarchive.store='muc_remote';
insert into group_owners (room, owner)
select
prosody.user||'@'||prosody.host as room,
reverse(split_part(reverse(split_part(prosody.value, '":"owner"',1)),'"',1)) as owner
from prosody
where
prosody.key='_affiliations' and (reverse(split_part(reverse(split_part(prosody.value, '":"owner"',1)),'"',1)) like '%@%');
insert into group_avatarids (room, avatarid)
select
prosody.user||'@'||prosody.host as room,
encode(digest(decode(split_part(split_part(split_part(prosody.value, 'BINVAL', 2), '__array":["', 2),'"',1), 'base64'), 'sha1'), 'hex') as groupavarid
from prosody where prosody.store='vcard_muc' and prosody.key='' and prosody.type='json' and prosody.host like 'conference.%';
create table if not exists no_notify_before(
username text,
before integer,
PRIMARY KEY (username)
);
create or replace rule upsert_no_notifiy as
on insert to no_notify_before where (exists (select 1 from no_notify_before where username=NEW.username))
do instead
update no_notify_before set
before=NEW.before
where username=NEW.username;
create or replace view user_notify_opt0 as
select prosody.user||'@'||prosody.host as email from prosody where
( prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"name":"document","__array":["\\"0%'
or prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"name":"document","__array":["\"0%' );
create or replace view user_notify_opt1 as
select prosody.user||'@'||prosody.host as email from prosody where (
prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"name":"document","__array":["\\"1%'
or prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"__array":["\\"1%'
or prosody.value like '%emailNotification"},"name":"document","__array":["\\"1%'
or prosody.value like '%emailNotification"},"__array":["\\"1%'
);
create or replace view user_notify_opt2 as
select prosody.user||'@'||prosody.host as email from prosody where (
prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"name":"document","__array":["\\"2%'
or prosody.value like '%emailNotification"},"name":"document","__array":["\\"2%'
or prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"__array":["\\"2%'
or prosody.value like '%emailNotification"},"__array":["\\"2%');
create or replace view user_notify_opt3 as
select prosody.user||'@'||prosody.host as email from prosody where (
prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"name":"document","__array":["\\"3%'
or prosody.value like '%emailNotification"},"name":"document","__array":["\\"3%'
or prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"__array":["\\"3%'
or prosody.value like '%emailNotification"},"__array":["\\"3%');
create or replace view user_notify_opt4 as
select prosody.user||'@'||prosody.host as email from prosody where (
prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"name":"document","__array":["\\"4%'
or prosody.value like '%emailNotification"},"name":"document","__array":["\\"4%'
or prosody.value like '%emailNotification","xmlns":"stanza:io:json"},"__array":["\\"4%'
or prosody.value like '%emailNotification"},"__array":["\\"4%');
create or replace view digest_messages as
select
prosodyarchive.user||'@'||prosodyarchive.host as email,
prosodyarchive.when as when,
(xpath('/message/@from | /jc:message/@from', prosodyarchive.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS from,
(xpath('/message/body/text() | /message/jc:body/text() | /jc:message/jc:body/text()', prosodyarchive.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS body,
(xpath('//message/@origtarget | /message/xc:vncTalkBroadcast/@origtarget', prosodyarchive.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text as broadcast,
(xpath('/message/xc:attachment | /jc:message/xc:attachment', prosodyarchive.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text AS x_attachment,
(xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', prosodyarchive.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text as x_vncConference,
(xpath('/message/@from | /jc:message/@from', prosodyarchive.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS target,
NULL as enddtime,
NULL as starttime,
'chat' as type
from prosodyarchive
where prosodyarchive.store='offline' and (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, prosodyarchive.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, prosodyarchive.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
union
select
recent_history_table.username as email,
mucarchive.when as when,
(xpath('/message/@from | /jc:message/@from', mucarchive.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS from,
(xpath('/message/body/text() | /message/jc:body/text() | /jc:message/jc:body/text()', mucarchive.value::xml, ARRAY[ARRAY['jc', 'jabber:client']]))[1]::text AS body,
(xpath('//message/@origtarget | /message/xc:vncTalkBroadcast/@origtarget', mucarchive.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk']]))[1]::text as broadcast,
(xpath('/message/xc:attachment | /jc:message/xc:attachment', mucarchive.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text AS x_attachment,
(xpath('/message/xc:vncTalkConference | /jc:message/xc:vncTalkConference', mucarchive.value::xml, ARRAY[ARRAY['xc', 'xmpp:vnctalk'], ARRAY['jc', 'jabber:client']]))[1]::text as x_vncConference,
recent_history_table.target as target,
recent_history_table.timestamp as endtime,
read_conversation.timestamp as starttime,
'groupchat' as type
from
recent_history_table, read_conversation
join prosodyarchive as mucarchive on (
mucarchive.user = split_part(read_conversation.target, '@', 1) and
mucarchive.host = split_part(read_conversation.target, '@', 2) and
mucarchive.store='muc_log' and
(xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, mucarchive.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text and
mucarchive.when > read_conversation.timestamp and
((xpath('//message/@from | //jc:message/@from'::text, mucarchive.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text
not in (select nickname from room_nick_jid_map where room_name=read_conversation.target and user_jid=read_conversation.username))
)
where
recent_history_table.type='groupchat'
AND recent_history_table.username=read_conversation.username
AND recent_history_table.target=read_conversation.target
AND (recent_history_table.timestamp > read_conversation.timestamp)
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, mucarchive.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text <> ''::text
AND (xpath('//body/text() | /jc:message/jc:body/text() | //jc:body/text()'::text, mucarchive.value::xml, ARRAY[ARRAY['jc'::text, 'jabber:client'::text]]))[1]::text IS NOT NULL
;
create or replace view room_membernames as SELECT room_nick_jid_map.room_name,
jsonb_agg(room_nick_jid_map.user_jid) AS members,
split_part(split_part(mucjoin.value, '"subject":'::text, 2), '"'::text, 2) AS title,
jsonb_agg(
CASE
WHEN ((((((split_part(split_part(split_part(namejoin.value, 'NICKNAME","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1) || ' '::text) || split_part(split_part(split_part(namejoin.value, 'FN","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1)) || ' '::text) || split_part(split_part(split_part(namejoin.value, 'GIVEN","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1)) || ' '::text) || split_part(split_part(split_part(namejoin.value, 'FAMILY","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1)) = ' '::text THEN namejoin."user"
ELSE (((((split_part(split_part(split_part(namejoin.value, 'NICKNAME","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1) || ' '::text) || split_part(split_part(split_part(namejoin.value, 'FN","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1)) || ' '::text) || split_part(split_part(split_part(namejoin.value, 'GIVEN","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1)) || ' '::text) || split_part(split_part(split_part(namejoin.value, 'FAMILY","'::text, 2), '__array":["'::text, 2), '"]}'::text, 1)
END) AS membernames
FROM room_nick_jid_map
LEFT JOIN prosody mucjoin ON room_nick_jid_map.room_name = ((mucjoin."user" || '@'::text) || mucjoin.host) AND mucjoin.store = 'config'::text AND mucjoin.key = '_data'::text AND mucjoin.value ~~ '%"subject":%'::text
LEFT JOIN prosody namejoin ON room_nick_jid_map.user_jid = ((namejoin."user" || '@'::text) || namejoin.host) AND namejoin.store = 'vcard'::text AND namejoin.key = ''::text AND namejoin.type = 'json'::text
GROUP BY room_nick_jid_map.room_name, mucjoin.value;
create table if not exists ep_author_ids (
username text,
authorid text,
primary key (username)
);
create or replace rule upsert_ep_author_ids as
on insert to ep_author_ids where (exists (select 1 from ep_author_ids where username=NEW.username))
do instead update ep_author_ids set
authorid=NEW.authorid
where username=NEW.username;
create table if not exists ep_conv_group (
convid text,
groupid text,
primary key (convid)
);
create or replace rule upsert_ep_conv_ids as
on insert to ep_conv_group where (exists (select 1 from ep_conv_group where convid=NEW.convid))
do instead update ep_conv_group set
groupid=NEW.groupid
where convid=NEW.convid;
create table if not exists ep_pad_names (
padid text,
groupid text,
padname text,
primary key (padid, groupid)
);
create or replace rule upsert_ep_pad_names as
on insert to ep_pad_names where (exists (select 1 from ep_pad_names where padid=NEW.padid and groupid=NEW.groupid))
do instead update ep_pad_names set
padname=NEW.padname
where padid=NEW.padid and groupid=NEW.groupid;
create table if not exists fcm_log(
id SERIAL,
msgid text,
fromjid text,
tojid text,
tofcmtoken text,
todevicetype text,
timestamp integer,
opstatus text,
primary key(id)
);
create table if not exists remote_muc_names (
username text,
remotemuc text,
displayname text,
primary key (username, remotemuc)
);
create table if not exists recordings (
convkey text,
callid text,
fileid text,
timestamp integer,
primary key (convkey, fileid, timestamp)
);
create or replace rule upsert_remote_muc_names as
on insert to remote_muc_names where (exists (select 1 from remote_muc_names where username=NEW.username and remotemuc=NEW.remotemuc))
do instead update remote_muc_names set
displayname = NEW.displayname
where username=NEW.username and remotemuc=NEW.remotemuc;
insert into remote_muc_names (username, remotemuc, displayname) select prosody.user||'@'||prosody.host as username, prosody.key as remotemuc, prosody.value as displayname from prosody where prosody.store='muc_remote' and not (prosody.key ilike '%/%');
create or replace rule update_muc_remote_name as
on insert to prosody where (NEW.store='muc_remote' and not (NEW.value ilike '%/%'))
do insert into remote_muc_names (username, remotemuc, displayname) select NEW.user||'@'||NEW.host as username, NEW.key as remotemuc, NEW.value as displayname;
create or replace view inverseconfmap as
select
split_part(split_part(value, 'value":"',2), '","', 1) as meeting,
conferencekey as key
from conferencemap;
--
-- Name: english_nostop; Type: TEXT SEARCH CONFIGURATION; Schema: public; Owner: postgres
--
CREATE TEXT SEARCH CONFIGURATION public.english_nostop (
PARSER = pg_catalog."default" );
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR asciiword WITH public.english_stem_nostop;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR word WITH public.english_stem_nostop;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR numword WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR email WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR url WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR host WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR sfloat WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR version WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR hword_numpart WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR hword_part WITH public.english_stem_nostop;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR hword_asciipart WITH public.english_stem_nostop;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR numhword WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR asciihword WITH public.english_stem_nostop;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR hword WITH public.english_stem_nostop;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR url_path WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR file WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR "float" WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR "int" WITH simple;
ALTER TEXT SEARCH CONFIGURATION public.english_nostop
ADD MAPPING FOR uint WITH simple;
CREATE SEQUENCE if not exists broadcast_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
update recent_history_table
set last_mention_time=upd.timestamp from
( select username, target, timestamp from
(select split_part(split_part(unnest(string_to_array(mention, ',')), '"xmpp:', 2), '"',1) as username,
room as target, processed_messages.date as timestamp from processed_messages where type='groupchat' and (mention is not null) order by processed_messages.date desc)
as mig where username ilike '%@%' ) as upd
where recent_history_table.username=upd.username and recent_history_table.target=upd.target;
create table if not exists surveymap (
id SERIAL,
groupchat text,
jid text,
survey text,
expiry integer,
PRIMARY KEY (id, groupchat, jid, survey)
);
create table if not exists surveyresults (
id SERIAL,
surveyid integer,
results text,
jid text,
PRIMARY KEY (id, jid)
);
create index ri3 on surveymap(expiry);
alter table surveymap add column name text;
alter table surveymap add column description text;
alter table surveymap add column status text;
alter table surveymap add column settings text;
alter table surveyresults add column status text;
create or replace rule upsert_survey_results as
on insert to surveyresults where (exists (select 1 from surveyresults where surveyid=NEW.surveyid and jid=NEW.jid))
do instead
update surveyresults set
results = NEW.results,
status = NEW.status
where surveyid = NEW.surveyid and jid=NEW.jid;
create index ri2 on surveyresults(surveyid);
create table if not exists emojireactions (
msgid text,
reactor text,
reaction text,
PRIMARY KEY (msgid, reactor)
);
create or replace rule upsert_emojireactions as
on insert to emojireactions where (exists (select 1 from emojireactions where msgid=NEW.msgid and reactor=NEW.reactor))
do instead
update emojireactions set
reaction = NEW.reaction
where msgid=NEW.msgid and reactor=NEW.reactor;
create table if not exists room_join_requests (
requester text,
room text,
date integer,
PRIMARY KEY (requester, room)
);
create or replace rule upsert_room_join_requests as
on insert to room_join_requests where (exists (select 1 from room_join_requests where requester=NEW.requester and room=NEW.room))
do instead
update room_join_requests set
date = extract(epoch from now())::integer
where requester=NEW.requester and room=NEW.room;
create table if not exists user_custom_images (
id SERIAL,
jid text,
updated integer,
type text,
filename text,
base64data text,
primary key(jid, filename)
);
create index uci0 on user_custom_images(id);
create index uci1 on user_custom_images(jid);
create index uci2 on user_custom_images(updated);