PostgreSQL rejects INSERT ... ON CONFLICT ... DO UPDATE on any table that has a conditional (WHERE) DO/DO ALSO rule or a non-NOTHING DO INSTEAD rule, erroring with 'INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules'. Prosody 13's mod_storage_sql uses ON CONFLICT upserts against the prosody kv table whenever prosody_unique_index exists (created by these scripts), so the five inherited conditional DO rules on prosody (cache_group_avatarids, update_profile_queue_from_insert/_update, update_muc_remote_name, update_room_nick_jid_map_remote) broke every kv upsert (vcard, vcard_muc, muc_remote, config, fcmtoken, ...). Replace those rules with AFTER INSERT / AFTER INSERT OR UPDATE row-level triggers, which do not block ON CONFLICT. The two profile-queue rules merge into one AFTER INSERT OR UPDATE trigger so the UPDATE branch of an ON CONFLICT upsert is also covered (it fires AFTER UPDATE triggers, not AFTER INSERT, when the conflict is taken). Also drop the legacy 0.11.6 update_group_owners rule. It is logically dead under 13.0.6 (fires on key='_affiliations', which is never written) but PostgreSQL checks rule existence at plan time, so even a dead conditional rule blocks ON CONFLICT. Dropping it is mandatory, not optional as the README previously claimed. Conversion is added to both prosody-13-new-deployment.sql (fresh deployments, and the run_new_deployment branch of migrate.sh) and prosody-13-rules-triggers.sql (the run_rules_triggers branch for 0.11.6->13.0 upgrades), so every helm pre/post-upgrade hook path reaches the fix. Derived-table INSTEAD upsert rules are unchanged. Verified against postgres:16: both scripts apply cleanly, pg_rewrite for prosody returns 0 rows, ON CONFLICT upserts succeed, and triggers fire on both INSERT and conflict-UPDATE branches; reproduced the production error by re-adding the legacy rules, then confirmed the incremental script resolves it. Idempotent re-run safe. Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/10>
2185 lines
112 KiB
PL/PgSQL
2185 lines
112 KiB
PL/PgSQL
-- =============================================================================
|
|
-- vnctalk-prosody 13.0.6 — full DB customization for NEW deployments
|
|
-- =============================================================================
|
|
-- This is the single idempotent script for fresh Prosody 13.0.6 deployments.
|
|
-- It REPLACES the two legacy 0.11.6 scripts:
|
|
-- - prosody-queries-noowner.sql.notifyfix
|
|
-- - prosody-trigger-noowner.sql
|
|
--
|
|
-- Differences from the legacy scripts:
|
|
-- * MUC rules/views/triggers adapted to the 13.0.6 storage layout
|
|
-- (affiliations stored one row per bare JID in the `prosody` `config`
|
|
-- store; `_affiliations` / `_occupants` keys no longer exist).
|
|
-- * `room_nicknames` view and `update_room_nick_jid_map` rule removed
|
|
-- (occupant data is no longer in the DB during normal operation;
|
|
-- `room_nick_jid_map` is derived purely from affiliations — all allowed
|
|
-- clients use their bare JID as their room nickname).
|
|
-- * `update_group_owners` rule replaced by a trigger (the legacy rule
|
|
-- conflicted with the existing `upsert_group_owners` INSTEAD rule).
|
|
-- * All `ALTER TABLE … ADD COLUMN` made idempotent (`IF NOT EXISTS`).
|
|
-- * One-time backfill INSERT/UPDATE statements removed (they matched
|
|
-- nothing on an empty DB and referenced the obsolete `_affiliations` key).
|
|
--
|
|
-- Idempotent: safe to re-run. Every table uses `IF NOT EXISTS`, every rule /
|
|
-- view / function uses `CREATE OR REPLACE`, and triggers are dropped before
|
|
-- re-creation. `ALTER TABLE … ADD COLUMN IF NOT EXISTS` is a no-op if the
|
|
-- column already exists.
|
|
--
|
|
-- Prerequisite: the Prosody storage tables `prosody` and `prosodyarchive`
|
|
-- must already exist (created by mod_storage_sql when Prosody first starts
|
|
-- against this DB). This script aborts if they are missing.
|
|
-- =============================================================================
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
do $$
|
|
begin
|
|
if not exists (select 1 from information_schema.tables where table_name = 'prosody') then
|
|
raise exception 'Table "prosody" does not exist. Start Prosody 13.0.6 once so mod_storage_sql creates the storage tables, then re-run this script.';
|
|
end if;
|
|
if not exists (select 1 from information_schema.tables where table_name = 'prosodyarchive') then
|
|
raise exception 'Table "prosodyarchive" does not exist. Start Prosody 13.0.6 once so mod_storage_sql creates the storage tables, then re-run this script.';
|
|
end if;
|
|
end $$;
|
|
|
|
create extension if not exists pgcrypto;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- prosody_unique_index: the unique index mod_storage_sql creates on the
|
|
-- `prosody` table (host, user, store, key). On a truly fresh DB Prosody
|
|
-- creates this itself via create_table(); re-creating it here with IF NOT
|
|
-- EXISTS is a harmless no-op in that case and fixes deployments where the
|
|
-- table was created by an older Prosody version without the unique index.
|
|
--
|
|
-- Without the unique index, mod_storage_sql falls back to SELECT-then-INSERT
|
|
-- instead of ON CONFLICT upsert, which can create duplicate rows under
|
|
-- concurrent writes (common in the fcmtoken map store). Deduplicate before
|
|
-- creating the index so it does not fail on pre-existing duplicates.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- Remove duplicate rows, keeping only the last-written row per
|
|
-- (host, user, store, key). ctid is the physical row identifier; the
|
|
-- highest ctid is the most recently inserted row. No-op if no duplicates.
|
|
delete from prosody
|
|
where ctid in (
|
|
select ctid from (
|
|
select ctid,
|
|
row_number() over (
|
|
partition by host, "user", store, key
|
|
order by ctid desc
|
|
) as rn
|
|
from prosody
|
|
) t
|
|
where rn > 1
|
|
);
|
|
|
|
create unique index if not exists prosody_unique_index
|
|
on prosody ("host", "user", "store", "key");
|
|
|
|
-- =============================================================================
|
|
-- TABLES
|
|
-- =============================================================================
|
|
|
|
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 if not exists si2 on processed_messages ("from");
|
|
create index if not exists si3 on processed_messages ("to");
|
|
create index if not exists si4 on processed_messages (owner);
|
|
create index if not exists si5 on processed_messages (broadcast);
|
|
create index if not exists si6 on processed_messages (room);
|
|
create index if not exists si7 on processed_messages (type);
|
|
create index if not exists si8 on processed_messages (id);
|
|
alter table processed_messages add column if not exists htmlbody text;
|
|
alter table processed_messages add column if not exists topicid text;
|
|
alter table processed_messages add column if not exists parent0 text;
|
|
alter table processed_messages add column if not exists parent text;
|
|
alter table processed_messages add column if not exists topic text;
|
|
alter table processed_messages add column if not exists updated integer;
|
|
alter table processed_messages add column if not exists broadcast_title text;
|
|
alter table processed_messages add column if not exists group_action text;
|
|
alter table processed_messages add column if not exists "encrypted" text;
|
|
alter table processed_messages add column if not exists encryption text;
|
|
alter table processed_messages add column if not exists reactions text;
|
|
alter table processed_messages add column if not exists expiry integer;
|
|
alter table processed_messages add column if not exists starredBy text[];
|
|
create index if not exists si9 on processed_messages (expiry);
|
|
create index if not exists di1 on processed_messages (date);
|
|
|
|
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 if not exists message_id text;
|
|
alter table recent_history_table add column if not exists received_receipt boolean;
|
|
alter table recent_history_table add column if not exists updated_at integer;
|
|
alter table recent_history_table add column if not exists mute_sound integer;
|
|
alter table recent_history_table add column if not exists mute_notification integer;
|
|
alter table recent_history_table add column if not exists has_data boolean;
|
|
alter table recent_history_table add column if not exists broadcast_title text;
|
|
alter table recent_history_table add column if not exists mentions text;
|
|
alter table recent_history_table add column if not exists sort_id bigint;
|
|
alter table recent_history_table add column if not exists deleted boolean;
|
|
alter table recent_history_table add column if not exists pad_read integer;
|
|
alter table recent_history_table add column if not exists has_pads boolean;
|
|
alter table recent_history_table add column if not exists last_mention_time integer;
|
|
alter table recent_history_table add column if not exists last_avatar_update integer;
|
|
alter table recent_history_table add column if not exists direct_e2e boolean;
|
|
alter table recent_history_table add column if not exists has_active_call boolean;
|
|
alter table recent_history_table add column if not exists last_callstate_update integer;
|
|
alter table recent_history_table add column if not exists x_conference_start integer;
|
|
alter table recent_history_table add column if not exists retention_time integer;
|
|
alter table recent_history_table add column if not exists x_conference_scheduler text;
|
|
alter table recent_history_table add column if not exists is_favourite boolean;
|
|
alter table recent_history_table add column if not exists is_pinned boolean;
|
|
alter table recent_history_table add column if not exists audience_only boolean;
|
|
alter table recent_history_table add column if not exists has_iom boolean;
|
|
alter table recent_history_table add column if not exists 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 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 if not exists tags jsonb;
|
|
alter table broadcast_audience add column if not exists description text;
|
|
|
|
create table if not exists group_owners (
|
|
room text,
|
|
owner text,
|
|
primary key (room)
|
|
);
|
|
alter table group_owners add column if not exists created integer;
|
|
alter table group_owners add column if not exists updated integer;
|
|
|
|
create table if not exists group_avatarids (
|
|
room text,
|
|
avatarid text,
|
|
primary key (room)
|
|
);
|
|
|
|
create table if not exists conferenceMapping (
|
|
conferenceKey text,
|
|
value text,
|
|
primary key (conferenceKey)
|
|
);
|
|
|
|
create table if not exists conferenceMap (
|
|
conferenceKey text,
|
|
value text,
|
|
primary key (conferenceKey)
|
|
);
|
|
|
|
create table if not exists exclude_from_history (
|
|
username text,
|
|
target text,
|
|
timestamp integer,
|
|
PRIMARY KEY (target, username)
|
|
);
|
|
|
|
create table if not exists read_conversation (
|
|
username text,
|
|
target text,
|
|
timestamp integer,
|
|
PRIMARY KEY (target, username)
|
|
);
|
|
|
|
create table if not exists read_pad (
|
|
username text,
|
|
target text,
|
|
timestamp integer,
|
|
PRIMARY KEY (target, username)
|
|
);
|
|
|
|
create table if not exists external_email_invites (
|
|
username text,
|
|
meeting text,
|
|
email text,
|
|
timestamp integer,
|
|
PRIMARY KEY (username, meeting, email)
|
|
);
|
|
|
|
drop view if exists total_mentions cascade;
|
|
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 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 if not exists since integer default 0;
|
|
|
|
create table if not exists unread_message_ids (
|
|
message_id text,
|
|
sender text,
|
|
receipient text,
|
|
timestamp integer,
|
|
primary key ( message_id, sender, receipient)
|
|
);
|
|
|
|
create table if not exists mute_conversation (
|
|
username text,
|
|
target text,
|
|
PRIMARY KEY (target, username)
|
|
);
|
|
|
|
create table if not exists mute_notification (
|
|
username text,
|
|
target text,
|
|
type integer,
|
|
PRIMARY KEY (target, username)
|
|
);
|
|
|
|
create table if not exists profile_update_queue (
|
|
username text,
|
|
timestamp integer,
|
|
PRIMARY KEY (username)
|
|
);
|
|
|
|
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 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 if not exists cri1 on call_tracking (receipient);
|
|
create index if not exists csi1 on call_tracking (started_at);
|
|
|
|
create table if not exists no_notify_before (
|
|
username text,
|
|
before integer,
|
|
PRIMARY KEY (username)
|
|
);
|
|
|
|
create table if not exists ep_author_ids (
|
|
username text,
|
|
authorid text,
|
|
primary key (username)
|
|
);
|
|
|
|
create table if not exists ep_conv_group (
|
|
convid text,
|
|
groupid text,
|
|
primary key (convid)
|
|
);
|
|
|
|
create table if not exists ep_pad_names (
|
|
padid text,
|
|
groupid text,
|
|
padname text,
|
|
primary key (padid, 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 table if not exists surveymap (
|
|
id SERIAL,
|
|
groupchat text,
|
|
jid text,
|
|
survey text,
|
|
expiry integer,
|
|
PRIMARY KEY (id, groupchat, jid, survey)
|
|
);
|
|
create index if not exists ri3 on surveymap(expiry);
|
|
alter table surveymap add column if not exists name text;
|
|
alter table surveymap add column if not exists description text;
|
|
alter table surveymap add column if not exists status text;
|
|
alter table surveymap add column if not exists settings text;
|
|
|
|
create table if not exists surveyresults (
|
|
id SERIAL,
|
|
surveyid integer,
|
|
results text,
|
|
jid text,
|
|
PRIMARY KEY (id, jid)
|
|
);
|
|
create index if not exists ri2 on surveyresults(surveyid);
|
|
alter table surveyresults add column if not exists status text;
|
|
|
|
create table if not exists emojireactions (
|
|
msgid text,
|
|
reactor text,
|
|
reaction text,
|
|
PRIMARY KEY (msgid, reactor)
|
|
);
|
|
|
|
create table if not exists room_join_requests (
|
|
requester text,
|
|
room text,
|
|
date integer,
|
|
PRIMARY KEY (requester, 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 if not exists uci0 on user_custom_images(id);
|
|
create index if not exists uci1 on user_custom_images(jid);
|
|
create index if not exists uci2 on user_custom_images(updated);
|
|
|
|
create table if not exists groupchat (
|
|
roomjid text NOT NULL,
|
|
roomtitle text,
|
|
PRIMARY KEY (roomjid)
|
|
);
|
|
|
|
create sequence if not exists broadcast_id_seq
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
-- =============================================================================
|
|
-- RULES — upsert / dedup rules on the derived tables
|
|
-- =============================================================================
|
|
|
|
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 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 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 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 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 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 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 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 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 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 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;
|
|
|
|
create or replace 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 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;
|
|
|
|
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 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 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
|
|
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
|
|
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 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;
|
|
|
|
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 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 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 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 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 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 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 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);
|
|
|
|
-- =============================================================================
|
|
-- RULES — on `prosody` (kv store)
|
|
-- =============================================================================
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- NOTE: the vcard_muc / vcard / muc_remote side-effects used to be conditional
|
|
-- `DO` rules on `prosody`. They have been converted to AFTER INSERT (/OR
|
|
-- UPDATE) triggers because PostgreSQL rejects `INSERT ... ON CONFLICT ...
|
|
-- DO UPDATE` on any table that carries a conditional (WHERE) DO/DO ALSO rule
|
|
-- or a non-NOTHING DO INSTEAD rule (error: "INSERT with ON CONFLICT clause
|
|
-- cannot be used with table that has INSERT or UPDATE rules"). Prosody 13's
|
|
-- mod_storage_sql uses ON CONFLICT upserts against the `prosody` kv table
|
|
-- whenever `prosody_unique_index` exists (created above), so the rules broke
|
|
-- every kv upsert. Triggers do not block ON CONFLICT. The derived-table
|
|
-- INSTEAD upsert rules (upsert_group_avatarids, upsert_profile_update_queue,
|
|
-- upsert_remote_muc_names, insert_room_nick_jid_map) are unaffected — they
|
|
-- live on the derived tables and keep rewriting the inserts emitted here.
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- vcard_muc room avatar cache (vcard_muc store still uses key='')
|
|
create or replace function fcache_group_avatarids()
|
|
returns trigger as $BODY$
|
|
begin
|
|
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 avatarid;
|
|
return NEW;
|
|
end;
|
|
$BODY$ language plpgsql volatile;
|
|
|
|
drop trigger if exists cache_group_avatarids on prosody;
|
|
create trigger cache_group_avatarids after insert on prosody
|
|
for each row
|
|
when (NEW.store = 'vcard_muc' and NEW.key = '' and NEW.type = 'json' and NEW.host like 'conference.%')
|
|
execute procedure fcache_group_avatarids();
|
|
|
|
-- vcard profile-update queue. Was two rules (on insert + on update); merged
|
|
-- into one AFTER INSERT OR UPDATE trigger so the UPDATE branch of an ON
|
|
-- CONFLICT upsert is also covered (an AFTER INSERT trigger alone would miss
|
|
-- the conflict-update path, since ON CONFLICT DO UPDATE fires AFTER UPDATE
|
|
-- triggers, not AFTER INSERT, when the conflict is taken).
|
|
create or replace function fupdate_profile_queue()
|
|
returns trigger as $BODY$
|
|
begin
|
|
insert into profile_update_queue (username, timestamp)
|
|
select
|
|
NEW.user || '@' || NEW.host as username,
|
|
extract(epoch from now())::integer as timestamp;
|
|
return NEW;
|
|
end;
|
|
$BODY$ language plpgsql volatile;
|
|
|
|
drop trigger if exists update_profile_queue on prosody;
|
|
create trigger update_profile_queue after insert or update on prosody
|
|
for each row
|
|
when (NEW.store = 'vcard' and NEW.type = 'json' and NEW.key = '')
|
|
execute procedure fupdate_profile_queue();
|
|
|
|
-- muc_remote nickname map (VNCtalk store)
|
|
create or replace function fupdate_muc_remote_name()
|
|
returns trigger as $BODY$
|
|
begin
|
|
insert into remote_muc_names (username, remotemuc, displayname)
|
|
select
|
|
NEW.user || '@' || NEW.host as username,
|
|
NEW.key as remotemuc,
|
|
NEW.value as displayname;
|
|
return NEW;
|
|
end;
|
|
$BODY$ language plpgsql volatile;
|
|
|
|
drop trigger if exists update_muc_remote_name on prosody;
|
|
create trigger update_muc_remote_name after insert on prosody
|
|
for each row
|
|
when (NEW.store = 'muc_remote' and not (NEW.value ilike '%/%'))
|
|
execute procedure fupdate_muc_remote_name();
|
|
|
|
create or replace function fupdate_room_nick_jid_map_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 as since;
|
|
return NEW;
|
|
end;
|
|
$BODY$ language plpgsql volatile;
|
|
|
|
drop trigger if exists update_room_nick_jid_map_remote on prosody;
|
|
create trigger update_room_nick_jid_map_remote after insert on prosody
|
|
for each row
|
|
when (NEW.store = 'muc_remote' and NEW.key like '%/%' and NEW.value like '%@%')
|
|
execute procedure fupdate_room_nick_jid_map_remote();
|
|
|
|
-- Drop any prior rule counterparts these triggers replace (no-op on a truly
|
|
-- fresh DB; cleans up a DB that previously ran an older script version).
|
|
drop rule if exists cache_group_avatarids on prosody;
|
|
drop rule if exists update_profile_queue_from_insert on prosody;
|
|
drop rule if exists update_profile_queue_from_update on prosody;
|
|
drop rule if exists update_muc_remote_name on prosody;
|
|
drop rule if exists update_room_nick_jid_map_remote on prosody;
|
|
-- Legacy 0.11.6 rule (fires on key='_affiliations', dead under 13.0.6).
|
|
-- PostgreSQL checks rule *existence* at plan time, so even this dead
|
|
-- conditional rule blocks ON CONFLICT — dropping it is mandatory.
|
|
drop rule if exists update_group_owners on prosody;
|
|
|
|
-- NOTE: group_owners is maintained by the `fupdate_group_owners` trigger fired
|
|
-- on the `_data` row (see trigger section below); the legacy
|
|
-- `update_group_owners` rule conflicted with the `upsert_group_owners`
|
|
-- INSTEAD rule and is dropped above.
|
|
|
|
-- =============================================================================
|
|
-- VIEWS (created early — several prosodyarchive rules below depend on them)
|
|
-- =============================================================================
|
|
|
|
-- room_membership: 13.0.6 layout — affiliations are one row per bare JID
|
|
-- (key=<jid>, value=<affiliation string>). Replaces the legacy
|
|
-- `where key='_affiliations'` + jsonb_object_keys form.
|
|
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 view if exists room_nicknames cascade;
|
|
|
|
-- =============================================================================
|
|
-- RULES — on `prosodyarchive`
|
|
-- =============================================================================
|
|
|
|
-- mentions
|
|
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;
|
|
|
|
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;
|
|
|
|
-- NOTE: the legacy `update_room_nick_jid_map` rule (which read the now-dropped
|
|
-- `room_nicknames` view) is GONE. room_nick_jid_map is maintained by the
|
|
-- `fupdate_room_nick_jid` trigger on the `config` store plus the invite rules
|
|
-- below.
|
|
|
|
-- room_nick_jid_map from invite messages (unchanged — XML-parsed)
|
|
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;
|
|
|
|
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;
|
|
|
|
-- single-chat recent history
|
|
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;
|
|
|
|
-- broadcast recent history
|
|
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;
|
|
|
|
-- broadcast audience
|
|
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);
|
|
|
|
-- groupchat recent history (uses room_membership view — see VIEWS section)
|
|
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;
|
|
|
|
drop rule if exists update_receipt_recent on prosodyarchive cascade;
|
|
|
|
-- mark conversations read
|
|
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;
|
|
|
|
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
|
|
;
|
|
|
|
-- unread message ids
|
|
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;
|
|
|
|
-- unread mention ids
|
|
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||'%'
|
|
;
|
|
|
|
create or replace rule insert_unread_message_ids_remotemuc_mention 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.%')
|
|
;
|
|
|
|
-- recent_history upsert rules
|
|
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));
|
|
|
|
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;
|
|
|
|
-- =============================================================================
|
|
-- VIEWS
|
|
-- =============================================================================
|
|
|
|
create or replace view total_mentions as
|
|
select
|
|
username,
|
|
target,
|
|
'groupchat' as type,
|
|
count(*) as total
|
|
from totalmentions group by username, target;
|
|
|
|
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 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 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
|
|
;
|
|
|
|
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 or replace view inverseconfmap as
|
|
select
|
|
split_part(split_part(value, 'value":"',2), '","', 1) as meeting,
|
|
conferencekey as key
|
|
from conferencemap;
|
|
|
|
-- =============================================================================
|
|
-- TEXT SEARCH CONFIGURATION
|
|
-- (guarded so the script is idempotent even if the dictionary/template is missing)
|
|
-- =============================================================================
|
|
|
|
do $$
|
|
begin
|
|
if not exists (select 1 from pg_ts_config where cfgname = 'english_nostop') then
|
|
create text search configuration public.english_nostop ( parser = pg_catalog."default" );
|
|
end if;
|
|
end $$;
|
|
|
|
-- Reconcile mappings idempotently: remove all existing mappings for this
|
|
-- configuration, then re-add the desired set (only if the dictionary exists).
|
|
-- NOTE: do NOT delete from pg_ts_config_map directly — it is a PostgreSQL
|
|
-- system catalog and only writable by superusers. ALTER ... DROP MAPPING
|
|
-- IF EXISTS is the non-superuser DDL that achieves the same effect.
|
|
do $$
|
|
begin
|
|
alter text search configuration public.english_nostop drop mapping if exists
|
|
for asciiword, word, hword_part, hword_asciipart, asciihword, hword,
|
|
numword, email, url, host, sfloat, version, hword_numpart, numhword,
|
|
url_path, file, "float", "int", uint;
|
|
if exists (select 1 from pg_ts_dict where dictname = 'english_stem_nostop') then
|
|
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 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 asciihword with public.english_stem_nostop;
|
|
alter text search configuration public.english_nostop add mapping for hword with public.english_stem_nostop;
|
|
end if;
|
|
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 numhword with simple;
|
|
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;
|
|
end $$;
|
|
|
|
-- =============================================================================
|
|
-- TRIGGER FUNCTIONS
|
|
-- =============================================================================
|
|
|
|
-- fprocess_messages — main message-processing trigger on prosodyarchive.
|
|
-- Body unchanged from 0.11.6; it relies on room_nick_jid_map (table) and
|
|
-- room_membership (view), both of which are kept correct by the 13.0.6
|
|
-- triggers/views above.
|
|
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;
|
|
|
|
drop trigger if exists process_messages on prosodyarchive;
|
|
create trigger process_messages after insert on prosodyarchive
|
|
for each row execute procedure fprocess_messages();
|
|
|
|
-- fupdate_room_nick_jid — 13.0.6: fires on the `_data` row (always written on
|
|
-- every room save) and rebuilds room_nick_jid_map from the per-affiliation
|
|
-- rows. Nickname = "<room@host>/<bare-jid>" (bare JID is the only nickname
|
|
-- used by allowed clients).
|
|
create or replace function fupdate_room_nick_jid()
|
|
returns trigger AS
|
|
$BODY$
|
|
DECLARE
|
|
_room text;
|
|
BEGIN
|
|
_room := NEW.user || '@' || NEW.host;
|
|
|
|
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')
|
|
);
|
|
|
|
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')
|
|
);
|
|
|
|
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;
|
|
|
|
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();
|
|
|
|
-- fupdate_group_owners — 13.0.6: replaces the legacy `update_group_owners`
|
|
-- rule (which keyed off `_affiliations` and conflicted with the
|
|
-- `upsert_group_owners` INSTEAD rule). Fires on the `_data` row and
|
|
-- reconciles group_owners from the per-affiliation owner rows.
|
|
create or replace function fupdate_group_owners()
|
|
returns trigger AS
|
|
$BODY$
|
|
DECLARE
|
|
_room text;
|
|
BEGIN
|
|
_room := NEW.user || '@' || NEW.host;
|
|
|
|
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'
|
|
);
|
|
|
|
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();
|
|
|
|
-- fupdate_room_nick_jid_remote (unchanged — VNCtalk muc_remote store)
|
|
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;
|
|
|
|
drop trigger if exists update_room_nick_jid_remote on prosody;
|
|
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();
|
|
|
|
-- fupdate_mention_stamp (unchanged)
|
|
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;
|
|
|
|
drop trigger if exists update_mention_stamp on totalmentions;
|
|
create trigger update_mention_stamp after insert on totalmentions
|
|
for each row execute procedure fupdate_mention_stamp();
|
|
|
|
-- fupdate_recent_avatar (unchanged — avatarupdate store)
|
|
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;
|
|
|
|
drop trigger if exists update_recent_avatar on prosody;
|
|
create trigger update_recent_avatar after insert on prosody
|
|
for each row
|
|
when (NEW.store='avatarupdate')
|
|
execute procedure fupdate_recent_avatar();
|
|
|
|
-- fupdate_iom_callstates (unchanged — callactive store)
|
|
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;
|
|
|
|
drop trigger if exists update_iom_callstates on prosody;
|
|
create trigger update_iom_callstates after insert on prosody
|
|
for each row
|
|
when (NEW.store='callactive')
|
|
execute procedure fupdate_iom_callstates();
|
|
|
|
-- fupdate_msg_reactions / fupdate_msg_delreactions (unchanged — emojireactions)
|
|
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;
|
|
|
|
drop trigger if exists update_msg_reactions on emojireactions;
|
|
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;
|
|
|
|
drop trigger if exists update_msg_delreactions on emojireactions;
|
|
create trigger update_msg_delreactions after delete on emojireactions
|
|
for each row execute procedure fupdate_msg_delreactions();
|
|
|
|
-- fupdate_groupchat (unchanged — _data row still written by 13.0.6)
|
|
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;
|
|
|
|
drop trigger if exists update_groupchat on prosody;
|
|
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();
|