fix(shangwutong): 完善访客资料同步
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
UPDATE custom_attribute_definitions
|
||||
SET
|
||||
attribute_display_name = 'Custom Priority',
|
||||
description = 'Custom ticket priority',
|
||||
attribute_values = '["Low", "Medium", "High"]'::jsonb,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE attribute_name = 'custom_priority'
|
||||
AND attribute_model = 'conversation_attribute'
|
||||
AND attribute_display_name = '自定义优先级'
|
||||
AND description = '自定义工单优先级'
|
||||
AND attribute_values = '["低", "中", "高"]'::jsonb;
|
||||
|
||||
UPDATE conversations
|
||||
SET
|
||||
custom_attributes = jsonb_set(
|
||||
custom_attributes,
|
||||
'{custom_priority}',
|
||||
to_jsonb(
|
||||
CASE custom_attributes->>'custom_priority'
|
||||
WHEN '低' THEN 'Low'
|
||||
WHEN '中' THEN 'Medium'
|
||||
WHEN '高' THEN 'High'
|
||||
END
|
||||
)
|
||||
),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE custom_attributes->>'custom_priority' IN ('低', '中', '高');
|
||||
@@ -0,0 +1,27 @@
|
||||
UPDATE custom_attribute_definitions
|
||||
SET
|
||||
attribute_display_name = '自定义优先级',
|
||||
description = '自定义工单优先级',
|
||||
attribute_values = '["低", "中", "高"]'::jsonb,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE attribute_name = 'custom_priority'
|
||||
AND attribute_model = 'conversation_attribute'
|
||||
AND attribute_display_name = 'Custom Priority'
|
||||
AND description = 'Custom ticket priority'
|
||||
AND attribute_values = '["Low", "Medium", "High"]'::jsonb;
|
||||
|
||||
UPDATE conversations
|
||||
SET
|
||||
custom_attributes = jsonb_set(
|
||||
custom_attributes,
|
||||
'{custom_priority}',
|
||||
to_jsonb(
|
||||
CASE custom_attributes->>'custom_priority'
|
||||
WHEN 'Low' THEN '低'
|
||||
WHEN 'Medium' THEN '中'
|
||||
WHEN 'High' THEN '高'
|
||||
END
|
||||
)
|
||||
),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE custom_attributes->>'custom_priority' IN ('Low', 'Medium', 'High');
|
||||
@@ -0,0 +1,12 @@
|
||||
DELETE FROM custom_attribute_definitions
|
||||
WHERE attribute_model = 'contact_attribute'
|
||||
AND attribute_name IN (
|
||||
'swt_ip', 'swt_ip_location', 'swt_isp', 'swt_resolution',
|
||||
'swt_language', 'swt_timezone', 'swt_os', 'swt_browser',
|
||||
'swt_browser_version', 'swt_user_agent', 'swt_device',
|
||||
'swt_query_title', 'swt_query_word', 'swt_traffic_source',
|
||||
'swt_profile_channel', 'swt_site_id'
|
||||
);
|
||||
|
||||
-- Contact values are intentionally retained in custom_attributes. Removing
|
||||
-- them would discard visitor diagnostics that may have arrived after upgrade.
|
||||
@@ -0,0 +1,83 @@
|
||||
INSERT INTO custom_attribute_definitions (
|
||||
account_id,
|
||||
attribute_name,
|
||||
attribute_display_name,
|
||||
attribute_type,
|
||||
attribute_model,
|
||||
description,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
accounts.id,
|
||||
definitions.attribute_name,
|
||||
definitions.attribute_display_name,
|
||||
'text',
|
||||
'contact_attribute',
|
||||
definitions.description,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM accounts
|
||||
CROSS JOIN (VALUES
|
||||
('swt_ip', '访客 IP', '商务通访客 IP 地址'),
|
||||
('swt_ip_location', 'IP 归属地', '商务通解析的访客 IP 归属地'),
|
||||
('swt_isp', '网络运营商', '商务通访客网络运营商'),
|
||||
('swt_resolution', '屏幕分辨率', '商务通访客设备屏幕分辨率'),
|
||||
('swt_language', '浏览器语言', '商务通访客浏览器语言'),
|
||||
('swt_timezone', '访客时区', '商务通访客设备时区'),
|
||||
('swt_os', '操作系统', '商务通访客设备操作系统'),
|
||||
('swt_browser', '浏览器', '商务通访客浏览器名称'),
|
||||
('swt_browser_version', '浏览器版本', '商务通访客浏览器版本'),
|
||||
('swt_user_agent', 'User Agent', '商务通访客浏览器 User Agent'),
|
||||
('swt_device', '访客设备', '商务通访客设备信息'),
|
||||
('swt_query_title', '搜索主题', '商务通访客进入页面时的搜索主题'),
|
||||
('swt_query_word', '搜索关键词', '商务通访客进入页面时的搜索关键词'),
|
||||
('swt_traffic_source', '流量来源', '商务通访客流量来源'),
|
||||
('swt_profile_channel', '来源渠道', '商务通访客来源渠道'),
|
||||
('swt_site_id', '商务通站点 ID', '商务通站点标识')
|
||||
) AS definitions(attribute_name, attribute_display_name, description)
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM custom_attribute_definitions existing
|
||||
WHERE existing.account_id = accounts.id
|
||||
AND existing.attribute_name = definitions.attribute_name
|
||||
AND existing.attribute_model = 'contact_attribute'
|
||||
AND existing.deleted_at IS NULL
|
||||
);
|
||||
|
||||
UPDATE contacts
|
||||
SET
|
||||
custom_attributes = COALESCE(custom_attributes, '{}'::jsonb)
|
||||
|| jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_ip', additional_attributes->'swt_ip',
|
||||
'swt_ip_location', additional_attributes->'swt_ip_location',
|
||||
'swt_isp', additional_attributes->'swt_isp',
|
||||
'swt_resolution', additional_attributes->'swt_resolution',
|
||||
'swt_language', additional_attributes->'swt_language',
|
||||
'swt_timezone', additional_attributes->'swt_timezone',
|
||||
'swt_os', additional_attributes->'swt_os',
|
||||
'swt_browser', additional_attributes->'swt_browser',
|
||||
'swt_browser_version', additional_attributes->'swt_browser_version',
|
||||
'swt_user_agent', additional_attributes->'swt_user_agent',
|
||||
'swt_device', additional_attributes->'swt_device',
|
||||
'swt_query_title', additional_attributes->'swt_query_title',
|
||||
'swt_query_word', additional_attributes->'swt_query_word',
|
||||
'swt_traffic_source', additional_attributes->'swt_traffic_source',
|
||||
'swt_profile_channel', additional_attributes->'swt_profile_channel',
|
||||
'swt_site_id', additional_attributes->'swt_site_id'
|
||||
)),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM contact_inboxes
|
||||
WHERE contact_inboxes.contact_id = contacts.id
|
||||
AND contact_inboxes.inbox_id IN (
|
||||
SELECT id FROM inboxes WHERE channel_type = 'shangwutong'
|
||||
)
|
||||
)
|
||||
AND COALESCE(additional_attributes, '{}'::jsonb) ?| ARRAY[
|
||||
'swt_ip', 'swt_ip_location', 'swt_isp', 'swt_resolution',
|
||||
'swt_language', 'swt_timezone', 'swt_os', 'swt_browser',
|
||||
'swt_browser_version', 'swt_user_agent', 'swt_device',
|
||||
'swt_query_title', 'swt_query_word', 'swt_traffic_source',
|
||||
'swt_profile_channel', 'swt_site_id'
|
||||
];
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Message sender names are denormalized presentation metadata. The original
|
||||
-- hard-coded values cannot be restored reliably after contacts are renamed.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,37 @@
|
||||
WITH latest_names AS (
|
||||
SELECT DISTINCT ON (conversation_id)
|
||||
conversation_id,
|
||||
NULLIF(BTRIM(additional_attributes->>'senderName'), '') AS visitor_name
|
||||
FROM messages
|
||||
WHERE message_type = 'incoming'
|
||||
AND NULLIF(BTRIM(additional_attributes->>'senderName'), '') IS NOT NULL
|
||||
AND BTRIM(additional_attributes->>'senderName') <> '商务通访客'
|
||||
ORDER BY conversation_id, created_at DESC, id DESC
|
||||
)
|
||||
UPDATE contacts
|
||||
SET
|
||||
name = latest_names.visitor_name,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
FROM conversations
|
||||
JOIN latest_names ON latest_names.conversation_id = conversations.id
|
||||
JOIN inboxes ON inboxes.id = conversations.inbox_id
|
||||
WHERE contacts.id = conversations.contact_id
|
||||
AND inboxes.channel_type = 'shangwutong'
|
||||
AND contacts.name <> latest_names.visitor_name;
|
||||
|
||||
-- Existing imported messages used a hard-coded senderName. Contact identity is
|
||||
-- the authoritative sender for incoming messages after this repair.
|
||||
UPDATE messages
|
||||
SET additional_attributes = jsonb_set(
|
||||
COALESCE(messages.additional_attributes, '{}'::jsonb),
|
||||
'{senderName}',
|
||||
to_jsonb(contacts.name)
|
||||
)
|
||||
FROM conversations
|
||||
JOIN contacts ON contacts.id = conversations.contact_id
|
||||
JOIN inboxes ON inboxes.id = conversations.inbox_id
|
||||
WHERE messages.conversation_id = conversations.id
|
||||
AND messages.message_type = 'incoming'
|
||||
AND inboxes.channel_type = 'shangwutong'
|
||||
AND BTRIM(contacts.name) <> ''
|
||||
AND COALESCE(messages.additional_attributes->>'senderName', '') <> contacts.name;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Percent-decoded visitor metadata cannot be losslessly restored to the
|
||||
-- original hex casing and plus-sign representation.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,74 @@
|
||||
CREATE OR REPLACE FUNCTION gochat_percent_decode(value TEXT)
|
||||
RETURNS TEXT
|
||||
LANGUAGE plpgsql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $$
|
||||
DECLARE
|
||||
position INTEGER := 1;
|
||||
encoded_bytes TEXT := '';
|
||||
token TEXT;
|
||||
BEGIN
|
||||
WHILE position <= char_length(value) LOOP
|
||||
token := substr(value, position, 1);
|
||||
IF token = '%' AND substr(value, position + 1, 2) ~ '^[0-9A-Fa-f]{2}$' THEN
|
||||
encoded_bytes := encoded_bytes || substr(value, position + 1, 2);
|
||||
position := position + 3;
|
||||
ELSIF token = '+' THEN
|
||||
encoded_bytes := encoded_bytes || '20';
|
||||
position := position + 1;
|
||||
ELSE
|
||||
encoded_bytes := encoded_bytes || encode(convert_to(token, 'UTF8'), 'hex');
|
||||
position := position + 1;
|
||||
END IF;
|
||||
END LOOP;
|
||||
RETURN convert_from(decode(encoded_bytes, 'hex'), 'UTF8');
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RETURN value;
|
||||
END;
|
||||
$$;
|
||||
|
||||
UPDATE contacts
|
||||
SET
|
||||
custom_attributes = COALESCE(custom_attributes, '{}'::jsonb)
|
||||
|| jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_ip_location', gochat_percent_decode(custom_attributes->>'swt_ip_location'),
|
||||
'swt_color_depth', gochat_percent_decode(custom_attributes->>'swt_color_depth'),
|
||||
'swt_resolution', gochat_percent_decode(custom_attributes->>'swt_resolution'),
|
||||
'swt_os', gochat_percent_decode(custom_attributes->>'swt_os'),
|
||||
'city', CASE
|
||||
WHEN COALESCE(custom_attributes->>'city', '') = ''
|
||||
THEN gochat_percent_decode(custom_attributes->>'swt_ip_location')
|
||||
END
|
||||
)),
|
||||
additional_attributes = COALESCE(additional_attributes, '{}'::jsonb)
|
||||
|| jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_ip_location', gochat_percent_decode(additional_attributes->>'swt_ip_location'),
|
||||
'swt_color_depth', gochat_percent_decode(additional_attributes->>'swt_color_depth'),
|
||||
'swt_resolution', gochat_percent_decode(additional_attributes->>'swt_resolution'),
|
||||
'swt_os', gochat_percent_decode(additional_attributes->>'swt_os'),
|
||||
'city', CASE
|
||||
WHEN COALESCE(additional_attributes->>'city', '') = ''
|
||||
THEN gochat_percent_decode(additional_attributes->>'swt_ip_location')
|
||||
END
|
||||
)),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM contact_inboxes
|
||||
JOIN inboxes ON inboxes.id = contact_inboxes.inbox_id
|
||||
WHERE contact_inboxes.contact_id = contacts.id
|
||||
AND inboxes.channel_type = 'shangwutong'
|
||||
)
|
||||
AND (
|
||||
COALESCE(custom_attributes->>'swt_ip_location', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
OR COALESCE(custom_attributes->>'swt_color_depth', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
OR COALESCE(custom_attributes->>'swt_resolution', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
OR COALESCE(custom_attributes->>'swt_os', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
OR COALESCE(additional_attributes->>'swt_ip_location', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
OR COALESCE(additional_attributes->>'swt_color_depth', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
OR COALESCE(additional_attributes->>'swt_resolution', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
OR COALESCE(additional_attributes->>'swt_os', '') ~ '%[0-9A-Fa-f]{2}'
|
||||
);
|
||||
|
||||
DROP FUNCTION gochat_percent_decode(TEXT);
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Repaired environment fields cannot be reconstructed to the previous
|
||||
-- misaligned values without reintroducing corrupt contact metadata.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,91 @@
|
||||
CREATE OR REPLACE FUNCTION gochat_percent_decode(value TEXT)
|
||||
RETURNS TEXT
|
||||
LANGUAGE plpgsql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $$
|
||||
DECLARE
|
||||
position INTEGER := 1;
|
||||
encoded_bytes TEXT := '';
|
||||
token TEXT;
|
||||
BEGIN
|
||||
WHILE position <= char_length(value) LOOP
|
||||
token := substr(value, position, 1);
|
||||
IF token = '%' AND substr(value, position + 1, 2) ~ '^[0-9A-Fa-f]{2}$' THEN
|
||||
encoded_bytes := encoded_bytes || substr(value, position + 1, 2);
|
||||
position := position + 3;
|
||||
ELSIF token = '+' THEN
|
||||
encoded_bytes := encoded_bytes || '20';
|
||||
position := position + 1;
|
||||
ELSE
|
||||
encoded_bytes := encoded_bytes || encode(convert_to(token, 'UTF8'), 'hex');
|
||||
position := position + 1;
|
||||
END IF;
|
||||
END LOOP;
|
||||
RETURN convert_from(decode(encoded_bytes, 'hex'), 'UTF8');
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RETURN value;
|
||||
END;
|
||||
$$;
|
||||
|
||||
WITH profiles AS (
|
||||
SELECT DISTINCT ON (contacts.id)
|
||||
contacts.id,
|
||||
split_part(
|
||||
COALESCE(
|
||||
NULLIF(contacts.custom_attributes->>'swt_xst_profile', ''),
|
||||
contacts.additional_attributes->>'swt_xst_profile'
|
||||
),
|
||||
'|||||',
|
||||
1
|
||||
) AS profile
|
||||
FROM contacts
|
||||
JOIN contact_inboxes ON contact_inboxes.contact_id = contacts.id
|
||||
JOIN inboxes ON inboxes.id = contact_inboxes.inbox_id
|
||||
WHERE inboxes.channel_type = 'shangwutong'
|
||||
AND COALESCE(
|
||||
NULLIF(contacts.custom_attributes->>'swt_xst_profile', ''),
|
||||
contacts.additional_attributes->>'swt_xst_profile'
|
||||
) IS NOT NULL
|
||||
), decoded AS (
|
||||
SELECT
|
||||
id,
|
||||
NULLIF(gochat_percent_decode(split_part(profile, chr(26), 5)), '') AS location,
|
||||
NULLIF(gochat_percent_decode(split_part(profile, chr(26), 11)), '') AS resolution,
|
||||
NULLIF(gochat_percent_decode(split_part(profile, chr(26), 12)), '') AS user_agent,
|
||||
NULLIF(gochat_percent_decode(split_part(profile, chr(26), 13)), '') AS ip_address
|
||||
FROM profiles
|
||||
)
|
||||
UPDATE contacts
|
||||
SET
|
||||
custom_attributes = COALESCE(contacts.custom_attributes, '{}'::jsonb)
|
||||
|| jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_ip_location', decoded.location,
|
||||
'city', decoded.location,
|
||||
'swt_resolution', decoded.resolution,
|
||||
'swt_user_agent', decoded.user_agent,
|
||||
'swt_device', decoded.user_agent,
|
||||
'swt_os', decoded.user_agent,
|
||||
'swt_ip', decoded.ip_address
|
||||
)),
|
||||
additional_attributes = COALESCE(contacts.additional_attributes, '{}'::jsonb)
|
||||
|| jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_ip_location', decoded.location,
|
||||
'city', decoded.location,
|
||||
'swt_resolution', decoded.resolution,
|
||||
'swt_user_agent', decoded.user_agent,
|
||||
'swt_device', decoded.user_agent,
|
||||
'swt_os', decoded.user_agent,
|
||||
'swt_ip', decoded.ip_address
|
||||
)),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
FROM decoded
|
||||
WHERE contacts.id = decoded.id
|
||||
AND (
|
||||
decoded.location IS NOT NULL
|
||||
OR decoded.resolution IS NOT NULL
|
||||
OR decoded.user_agent IS NOT NULL
|
||||
OR decoded.ip_address IS NOT NULL
|
||||
);
|
||||
|
||||
DROP FUNCTION gochat_percent_decode(TEXT);
|
||||
@@ -0,0 +1,6 @@
|
||||
DELETE FROM custom_attribute_definitions
|
||||
WHERE attribute_name = 'swt_environment_version'
|
||||
AND attribute_model = 'contact_attribute';
|
||||
|
||||
-- The previous swt_isp and duplicated UA-derived values were semantically
|
||||
-- incorrect and are intentionally not restored.
|
||||
@@ -0,0 +1,116 @@
|
||||
INSERT INTO custom_attribute_definitions (
|
||||
account_id,
|
||||
attribute_name,
|
||||
attribute_display_name,
|
||||
attribute_type,
|
||||
attribute_model,
|
||||
description,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
accounts.id,
|
||||
'swt_environment_version',
|
||||
'环境协议版本',
|
||||
'text',
|
||||
'contact_attribute',
|
||||
'商务通 kind=7 环境信息格式版本;不是网络运营商',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM accounts
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM custom_attribute_definitions existing
|
||||
WHERE existing.account_id = accounts.id
|
||||
AND existing.attribute_name = 'swt_environment_version'
|
||||
AND existing.attribute_model = 'contact_attribute'
|
||||
AND existing.deleted_at IS NULL
|
||||
);
|
||||
|
||||
DELETE FROM custom_attribute_definitions
|
||||
WHERE attribute_name = 'swt_isp'
|
||||
AND attribute_model = 'contact_attribute';
|
||||
|
||||
WITH swt_contacts AS (
|
||||
SELECT DISTINCT contacts.id
|
||||
FROM contacts
|
||||
JOIN contact_inboxes ON contact_inboxes.contact_id = contacts.id
|
||||
JOIN inboxes ON inboxes.id = contact_inboxes.inbox_id
|
||||
WHERE inboxes.channel_type = 'shangwutong'
|
||||
), source AS (
|
||||
SELECT
|
||||
contacts.id,
|
||||
COALESCE(
|
||||
NULLIF(contacts.custom_attributes->>'swt_user_agent', ''),
|
||||
NULLIF(contacts.additional_attributes->>'swt_user_agent', ''),
|
||||
NULLIF(contacts.custom_attributes->>'swt_os', ''),
|
||||
NULLIF(contacts.additional_attributes->>'swt_os', '')
|
||||
) AS user_agent,
|
||||
COALESCE(
|
||||
NULLIF(contacts.custom_attributes->>'swt_environment_version', ''),
|
||||
NULLIF(contacts.custom_attributes->>'swt_isp', ''),
|
||||
NULLIF(contacts.additional_attributes->>'swt_environment_version', ''),
|
||||
NULLIF(contacts.additional_attributes->>'swt_isp', '')
|
||||
) AS environment_version
|
||||
FROM contacts
|
||||
JOIN swt_contacts ON swt_contacts.id = contacts.id
|
||||
), parsed AS (
|
||||
SELECT
|
||||
id,
|
||||
user_agent,
|
||||
environment_version,
|
||||
CASE
|
||||
WHEN user_agent ~ 'OpenHarmony[[:space:]]+[0-9.]+'
|
||||
THEN 'OpenHarmony ' || substring(user_agent FROM 'OpenHarmony[[:space:]]+([0-9.]+)')
|
||||
WHEN user_agent ~ 'Android[[:space:]]+[^;]+'
|
||||
THEN 'Android ' || BTRIM(substring(user_agent FROM 'Android[[:space:]]+([^;]+)'))
|
||||
WHEN user_agent ~ 'iPhone OS[[:space:]]+[0-9_]+'
|
||||
THEN 'iOS ' || replace(substring(user_agent FROM 'iPhone OS[[:space:]]+([0-9_]+)'), '_', '.')
|
||||
WHEN user_agent LIKE '%Windows NT 10.0%'
|
||||
THEN 'Windows 10/11'
|
||||
WHEN user_agent LIKE '%Windows%'
|
||||
THEN 'Windows'
|
||||
WHEN user_agent ~ 'Mac OS X[[:space:]]+[0-9_]+'
|
||||
THEN 'macOS ' || replace(substring(user_agent FROM 'Mac OS X[[:space:]]+([0-9_]+)'), '_', '.')
|
||||
WHEN user_agent LIKE '%Linux%'
|
||||
THEN 'Linux'
|
||||
END AS operating_system,
|
||||
CASE
|
||||
WHEN user_agent LIKE '%OpenHarmony%'
|
||||
THEN BTRIM(split_part(user_agent, ';', 1))
|
||||
WHEN user_agent ~ 'Android[[:space:]]+[^;]+;[[:space:]]*[^;]+ Build/'
|
||||
THEN BTRIM(substring(user_agent FROM 'Android[[:space:]]+[^;]+;[[:space:]]*([^;]+) Build/'))
|
||||
WHEN user_agent LIKE '%Android%'
|
||||
THEN 'Android 设备'
|
||||
WHEN user_agent LIKE '%iPhone OS%'
|
||||
THEN 'iPhone'
|
||||
WHEN user_agent LIKE '%Windows%'
|
||||
THEN 'Windows PC'
|
||||
WHEN user_agent LIKE '%Mac OS X%'
|
||||
THEN 'Mac'
|
||||
WHEN user_agent LIKE '%Linux%'
|
||||
THEN 'Linux 设备'
|
||||
END AS device
|
||||
FROM source
|
||||
)
|
||||
UPDATE contacts
|
||||
SET
|
||||
custom_attributes = (
|
||||
COALESCE(contacts.custom_attributes, '{}'::jsonb) - 'swt_isp' - 'swt_color_depth'
|
||||
) || jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_environment_version', parsed.environment_version,
|
||||
'swt_user_agent', parsed.user_agent,
|
||||
'swt_os', parsed.operating_system,
|
||||
'swt_device', parsed.device
|
||||
)),
|
||||
additional_attributes = (
|
||||
COALESCE(contacts.additional_attributes, '{}'::jsonb) - 'swt_isp' - 'swt_color_depth'
|
||||
) || jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_environment_version', parsed.environment_version,
|
||||
'swt_user_agent', parsed.user_agent,
|
||||
'swt_os', parsed.operating_system,
|
||||
'swt_device', parsed.device
|
||||
)),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
FROM parsed
|
||||
WHERE contacts.id = parsed.id;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- The previous device values duplicated the full User Agent and were invalid.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,75 @@
|
||||
WITH swt_contacts AS (
|
||||
SELECT DISTINCT contacts.id
|
||||
FROM contacts
|
||||
JOIN contact_inboxes ON contact_inboxes.contact_id = contacts.id
|
||||
JOIN inboxes ON inboxes.id = contact_inboxes.inbox_id
|
||||
WHERE inboxes.channel_type = 'shangwutong'
|
||||
), source AS (
|
||||
SELECT
|
||||
contacts.id,
|
||||
COALESCE(
|
||||
NULLIF(contacts.custom_attributes->>'swt_user_agent', ''),
|
||||
NULLIF(contacts.additional_attributes->>'swt_user_agent', ''),
|
||||
NULLIF(contacts.custom_attributes->>'swt_device', ''),
|
||||
NULLIF(contacts.additional_attributes->>'swt_device', '')
|
||||
) AS user_agent
|
||||
FROM contacts
|
||||
JOIN swt_contacts ON swt_contacts.id = contacts.id
|
||||
), parsed AS (
|
||||
SELECT
|
||||
id,
|
||||
user_agent,
|
||||
CASE
|
||||
WHEN user_agent ~ 'OpenHarmony[[:space:]]+[0-9.]+'
|
||||
THEN 'OpenHarmony ' || substring(user_agent FROM 'OpenHarmony[[:space:]]+([0-9.]+)')
|
||||
WHEN user_agent ~ 'Android[[:space:]]+[^;]+'
|
||||
THEN 'Android ' || BTRIM(substring(user_agent FROM 'Android[[:space:]]+([^;]+)'))
|
||||
WHEN user_agent ~ 'iPhone OS[[:space:]]+[0-9_]+'
|
||||
THEN 'iOS ' || replace(substring(user_agent FROM 'iPhone OS[[:space:]]+([0-9_]+)'), '_', '.')
|
||||
WHEN user_agent LIKE '%Windows NT 10.0%'
|
||||
THEN 'Windows 10/11'
|
||||
WHEN user_agent LIKE '%Windows%'
|
||||
THEN 'Windows'
|
||||
WHEN user_agent ~ 'Mac OS X[[:space:]]+[0-9_]+'
|
||||
THEN 'macOS ' || replace(substring(user_agent FROM 'Mac OS X[[:space:]]+([0-9_]+)'), '_', '.')
|
||||
WHEN user_agent LIKE '%Linux%'
|
||||
THEN 'Linux'
|
||||
END AS operating_system,
|
||||
CASE
|
||||
WHEN user_agent LIKE '%OpenHarmony%'
|
||||
THEN BTRIM(split_part(user_agent, ';', 1))
|
||||
WHEN user_agent ~ 'Android[[:space:]]+[^;]+;[[:space:]]*[^;]+ Build/'
|
||||
THEN BTRIM(substring(user_agent FROM 'Android[[:space:]]+[^;]+;[[:space:]]*([^;]+) Build/'))
|
||||
WHEN user_agent ~ 'Android[[:space:]]+[^;]+;[[:space:]]*[^;]+;[[:space:]]*[^;]+ Build/'
|
||||
THEN BTRIM(substring(user_agent FROM 'Android[[:space:]]+[^;]+;[[:space:]]*[^;]+;[[:space:]]*([^;]+) Build/'))
|
||||
WHEN user_agent LIKE '%Android%'
|
||||
THEN 'Android 设备'
|
||||
WHEN user_agent LIKE '%iPhone OS%'
|
||||
THEN 'iPhone'
|
||||
WHEN user_agent LIKE '%Windows%'
|
||||
THEN 'Windows PC'
|
||||
WHEN user_agent LIKE '%Mac OS X%'
|
||||
THEN 'Mac'
|
||||
WHEN user_agent LIKE '%Linux%'
|
||||
THEN 'Linux 设备'
|
||||
END AS device
|
||||
FROM source
|
||||
WHERE user_agent IS NOT NULL
|
||||
)
|
||||
UPDATE contacts
|
||||
SET
|
||||
custom_attributes = COALESCE(contacts.custom_attributes, '{}'::jsonb)
|
||||
|| jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_user_agent', parsed.user_agent,
|
||||
'swt_device', parsed.device,
|
||||
'swt_os', parsed.operating_system
|
||||
)),
|
||||
additional_attributes = COALESCE(contacts.additional_attributes, '{}'::jsonb)
|
||||
|| jsonb_strip_nulls(jsonb_build_object(
|
||||
'swt_user_agent', parsed.user_agent,
|
||||
'swt_device', parsed.device,
|
||||
'swt_os', parsed.operating_system
|
||||
)),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
FROM parsed
|
||||
WHERE contacts.id = parsed.id;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Country metadata inferred from IP location cannot be safely distinguished from
|
||||
-- user-entered values, so this data repair is intentionally irreversible.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,22 @@
|
||||
UPDATE contacts
|
||||
SET
|
||||
country_code = 'CN',
|
||||
location = CASE
|
||||
WHEN COALESCE(location, '') = '' THEN custom_attributes->>'swt_ip_location'
|
||||
ELSE location
|
||||
END,
|
||||
additional_attributes = COALESCE(additional_attributes, '{}'::jsonb)
|
||||
|| jsonb_build_object(
|
||||
'country_code', 'CN',
|
||||
'country', 'China',
|
||||
'city', custom_attributes->>'swt_ip_location'
|
||||
),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM contact_inboxes
|
||||
JOIN inboxes ON inboxes.id = contact_inboxes.inbox_id
|
||||
WHERE contact_inboxes.contact_id = contacts.id
|
||||
AND inboxes.channel_type = 'shangwutong'
|
||||
)
|
||||
AND COALESCE(custom_attributes->>'swt_ip_location', '') ~ '^(北京|天津|上海|重庆|河北|山西|辽宁|吉林|黑龙江|江苏|浙江|安徽|福建|江西|山东|河南|湖北|湖南|广东|海南|四川|贵州|云南|陕西|甘肃|青海|台湾|内蒙古|广西|西藏|宁夏|新疆|香港|澳门)';
|
||||
Reference in New Issue
Block a user