221 lines
5.4 KiB
JavaScript
221 lines
5.4 KiB
JavaScript
import crypto from 'node:crypto';
|
|
|
|
export const PUSH_PROVIDERS = {
|
|
FCM: 'FCM',
|
|
APNS: 'APNS',
|
|
WEB_PUSH: 'WEB_PUSH',
|
|
};
|
|
|
|
export const PUSH_PLATFORMS = {
|
|
IOS: 'IOS',
|
|
ANDROID: 'ANDROID',
|
|
WEB: 'WEB',
|
|
};
|
|
|
|
export function hashPushToken(pushToken) {
|
|
return crypto.createHash('sha256').update(`${pushToken || ''}`).digest('hex');
|
|
}
|
|
|
|
export async function registerPushToken(client, {
|
|
tenantId,
|
|
userId,
|
|
staffId = null,
|
|
businessMembershipId = null,
|
|
vendorMembershipId = null,
|
|
provider = PUSH_PROVIDERS.FCM,
|
|
platform,
|
|
pushToken,
|
|
deviceId = null,
|
|
appVersion = null,
|
|
appBuild = null,
|
|
locale = null,
|
|
timezone = null,
|
|
notificationsEnabled = true,
|
|
metadata = {},
|
|
}) {
|
|
const tokenHash = hashPushToken(pushToken);
|
|
const result = await client.query(
|
|
`
|
|
INSERT INTO device_push_tokens (
|
|
tenant_id,
|
|
user_id,
|
|
staff_id,
|
|
business_membership_id,
|
|
vendor_membership_id,
|
|
provider,
|
|
platform,
|
|
push_token,
|
|
token_hash,
|
|
device_id,
|
|
app_version,
|
|
app_build,
|
|
locale,
|
|
timezone,
|
|
notifications_enabled,
|
|
invalidated_at,
|
|
invalidation_reason,
|
|
last_registered_at,
|
|
last_seen_at,
|
|
metadata
|
|
)
|
|
VALUES (
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NULL, NULL, NOW(), NOW(), $16::jsonb
|
|
)
|
|
ON CONFLICT (provider, token_hash) DO UPDATE
|
|
SET tenant_id = EXCLUDED.tenant_id,
|
|
user_id = EXCLUDED.user_id,
|
|
staff_id = EXCLUDED.staff_id,
|
|
business_membership_id = EXCLUDED.business_membership_id,
|
|
vendor_membership_id = EXCLUDED.vendor_membership_id,
|
|
platform = EXCLUDED.platform,
|
|
push_token = EXCLUDED.push_token,
|
|
device_id = EXCLUDED.device_id,
|
|
app_version = EXCLUDED.app_version,
|
|
app_build = EXCLUDED.app_build,
|
|
locale = EXCLUDED.locale,
|
|
timezone = EXCLUDED.timezone,
|
|
notifications_enabled = EXCLUDED.notifications_enabled,
|
|
invalidated_at = NULL,
|
|
invalidation_reason = NULL,
|
|
last_registered_at = NOW(),
|
|
last_seen_at = NOW(),
|
|
metadata = COALESCE(device_push_tokens.metadata, '{}'::jsonb) || EXCLUDED.metadata,
|
|
updated_at = NOW()
|
|
RETURNING id,
|
|
tenant_id AS "tenantId",
|
|
user_id AS "userId",
|
|
staff_id AS "staffId",
|
|
business_membership_id AS "businessMembershipId",
|
|
vendor_membership_id AS "vendorMembershipId",
|
|
provider,
|
|
platform,
|
|
device_id AS "deviceId",
|
|
notifications_enabled AS "notificationsEnabled"
|
|
`,
|
|
[
|
|
tenantId,
|
|
userId,
|
|
staffId,
|
|
businessMembershipId,
|
|
vendorMembershipId,
|
|
provider,
|
|
platform,
|
|
pushToken,
|
|
tokenHash,
|
|
deviceId,
|
|
appVersion,
|
|
appBuild,
|
|
locale,
|
|
timezone,
|
|
notificationsEnabled,
|
|
JSON.stringify(metadata || {}),
|
|
]
|
|
);
|
|
|
|
return result.rows[0];
|
|
}
|
|
|
|
export async function unregisterPushToken(client, {
|
|
tenantId,
|
|
userId,
|
|
tokenId = null,
|
|
pushToken = null,
|
|
reason = 'USER_REQUESTED',
|
|
}) {
|
|
const tokenHash = pushToken ? hashPushToken(pushToken) : null;
|
|
const result = await client.query(
|
|
`
|
|
UPDATE device_push_tokens
|
|
SET notifications_enabled = FALSE,
|
|
invalidated_at = NOW(),
|
|
invalidation_reason = $4,
|
|
updated_at = NOW()
|
|
WHERE tenant_id = $1
|
|
AND user_id = $2
|
|
AND (
|
|
($3::uuid IS NOT NULL AND id = $3::uuid)
|
|
OR
|
|
($5::text IS NOT NULL AND token_hash = $5::text)
|
|
)
|
|
RETURNING id,
|
|
provider,
|
|
platform,
|
|
device_id AS "deviceId"
|
|
`,
|
|
[tenantId, userId, tokenId, reason, tokenHash]
|
|
);
|
|
|
|
return result.rows;
|
|
}
|
|
|
|
export async function resolveNotificationTargetTokens(client, notification) {
|
|
const result = await client.query(
|
|
`
|
|
WITH recipient_users AS (
|
|
SELECT $2::text AS user_id
|
|
WHERE $2::text IS NOT NULL
|
|
UNION
|
|
SELECT bm.user_id
|
|
FROM business_memberships bm
|
|
WHERE $3::uuid IS NOT NULL
|
|
AND bm.id = $3::uuid
|
|
UNION
|
|
SELECT s.user_id
|
|
FROM staffs s
|
|
WHERE $4::uuid IS NOT NULL
|
|
AND s.id = $4::uuid
|
|
)
|
|
SELECT
|
|
dpt.id,
|
|
dpt.user_id AS "userId",
|
|
dpt.staff_id AS "staffId",
|
|
dpt.provider,
|
|
dpt.platform,
|
|
dpt.push_token AS "pushToken",
|
|
dpt.device_id AS "deviceId",
|
|
dpt.metadata
|
|
FROM device_push_tokens dpt
|
|
JOIN recipient_users ru ON ru.user_id = dpt.user_id
|
|
WHERE dpt.tenant_id = $1
|
|
AND dpt.notifications_enabled = TRUE
|
|
AND dpt.invalidated_at IS NULL
|
|
ORDER BY dpt.last_seen_at DESC, dpt.created_at DESC
|
|
`,
|
|
[
|
|
notification.tenant_id,
|
|
notification.recipient_user_id,
|
|
notification.recipient_business_membership_id,
|
|
notification.recipient_staff_id,
|
|
]
|
|
);
|
|
|
|
return result.rows;
|
|
}
|
|
|
|
export async function markPushTokenInvalid(client, tokenId, reason) {
|
|
await client.query(
|
|
`
|
|
UPDATE device_push_tokens
|
|
SET notifications_enabled = FALSE,
|
|
invalidated_at = NOW(),
|
|
invalidation_reason = $2,
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
`,
|
|
[tokenId, reason]
|
|
);
|
|
}
|
|
|
|
export async function touchPushTokenDelivery(client, tokenId) {
|
|
await client.query(
|
|
`
|
|
UPDATE device_push_tokens
|
|
SET last_delivery_at = NOW(),
|
|
last_seen_at = NOW(),
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
`,
|
|
[tokenId]
|
|
);
|
|
}
|