diff --git a/apps/frontend/src/constants/routes.ts b/apps/frontend/src/constants/routes.ts index 21134ac..e871935 100644 --- a/apps/frontend/src/constants/routes.ts +++ b/apps/frontend/src/constants/routes.ts @@ -2,6 +2,7 @@ export const ROUTE_NAMES = { HOME: 'home', CREATE: 'create', MANAGE_QR_CODE_READY: 'manage-qr-code-ready', + MANAGE_QR_CODE_CHAT: 'manage-qr-code-chat', READ_QR_CODE: 'read-qr-code', DONATE: 'donate', PUBLIC_CHAT: 'public-chat', diff --git a/apps/frontend/src/router/index.ts b/apps/frontend/src/router/index.ts index 753b8b1..09f117a 100644 --- a/apps/frontend/src/router/index.ts +++ b/apps/frontend/src/router/index.ts @@ -19,6 +19,12 @@ const routes = [ component: () => import('@/views/ManageQRCodeReadyView.vue'), props: true, }, + { + path: '/manage/:qr_code_uri/chat/:chat', + name: ROUTE_NAMES.MANAGE_QR_CODE_CHAT, + component: () => import('@/views/ManageQRCodeChatView.vue'), + props: true, + }, { path: '/read/:qr_code_uri', name: ROUTE_NAMES.READ_QR_CODE, diff --git a/apps/frontend/src/views/ManageQRCodeChatView.vue b/apps/frontend/src/views/ManageQRCodeChatView.vue new file mode 100644 index 0000000..63011c8 --- /dev/null +++ b/apps/frontend/src/views/ManageQRCodeChatView.vue @@ -0,0 +1,51 @@ + + + diff --git a/packages/types/DBDocuments/QRCodeDocument.ts b/packages/types/DBDocuments/QRCodeDocument.ts index 8b6a073..29f5999 100644 --- a/packages/types/DBDocuments/QRCodeDocument.ts +++ b/packages/types/DBDocuments/QRCodeDocument.ts @@ -1,44 +1,47 @@ -import type { BaseDocument } from './BaseDocument' +import type { BaseDocument } from "./BaseDocument"; export type ContactInfo = { - name?: string - phone?: string - email?: string -} + name?: string; + phone?: string; + email?: string; +}; /** * Интерфейс для документа QR‑кода. * Представляет QR‑код, созданный пользователем, и информацию о привязанном объекте. */ export interface QRCodeDocument extends BaseDocument { + /** Идентификатор документа, в формате "qr_code:" */ + _id: string; + /** Тип документа, всегда 'qr_code' */ - type: 'qr_code' + type: "qr_code"; /** Уникальный URL для настройки и взаимодействия */ - uri: string + uri: string; /** Ссылка на QR‑код */ - url: string + url: string; /** Идентификатор пользователя, который создал QR‑код */ - user_uuid: string + user_uuid: string; /** Идентификатор браузера, через который был создан QR-код */ - browser_uuid: string + browser_uuid: string; /** Название объекта, для которого создан QR‑код, например "Мой автомобиль" */ - name: string + name: string; - messageDeliveryMethod: 'telegram' | 'webPush' + messageDeliveryMethod: "telegram" | "webPush"; /** Тип объекта, для которого создан QR‑код, например "car" */ - placement: 'car' | 'pet' | 'door' | 'store' | 'restaurant_table' | 'other' + placement: "car" | "pet" | "door" | "store" | "restaurant_table" | "other"; /** Доступные действия для гостей (например, ["send_message"]) */ - actions: string[] + actions: string[]; /** Предустановленное сообщение для гостей */ - predefinedMessages?: string[] + predefinedMessages?: string[]; - contactInfo?: ContactInfo + contactInfo?: ContactInfo; } diff --git a/services/message-delivery-method-web-push/src/dbs.ts b/services/message-delivery-method-web-push/src/dbs.ts index 2f367cc..296d944 100644 --- a/services/message-delivery-method-web-push/src/dbs.ts +++ b/services/message-delivery-method-web-push/src/dbs.ts @@ -3,6 +3,7 @@ import type { MessageDocument, QRCodeDocument, ServerSettingsDocument, + WebPushSubscriptionDocument, } from "@hereconnect/types"; export interface ServiceDoc { @@ -26,6 +27,13 @@ export const qrDb = new PouchDB(`${process.env.API_URL}/qr`, { skip_setup: true, }); +export const webPushSubscriptionsDb = new PouchDB( + `${process.env.API_URL}/web_push_subscriptions`, + { + skip_setup: true, + }, +); + export const { messagesDb, serviceDb } = (() => { const messagesDB = new PouchDB(messagesDbUrl, { skip_setup: true, diff --git a/services/message-delivery-method-web-push/src/server.ts b/services/message-delivery-method-web-push/src/server.ts index 61c5a43..5603caf 100644 --- a/services/message-delivery-method-web-push/src/server.ts +++ b/services/message-delivery-method-web-push/src/server.ts @@ -1,7 +1,7 @@ import webpush from "web-push"; import { CouchDBChangesStream } from "couchdb-changes-stream"; -import { qrDb, messagesDbUrl } from "./dbs"; +import { qrDb, messagesDbUrl, webPushSubscriptionsDb } from "./dbs"; import { setupVapidDetails } from "./setupVapidDetails"; import type { MessageDocument } from "@hereconnect/types"; import { ServiceSeqTracker } from "./serviceSeqTracker"; @@ -36,9 +36,9 @@ const start = async () => { console.info("Service started from sequence", since); for await (const change of changesStream) { - const { seq, doc } = change; + const { seq, doc: msgDoc } = change; - if (!doc) { + if (!msgDoc) { console.error("Change missing document:", change); continue; } @@ -46,28 +46,69 @@ const start = async () => { serviceSeqTracker.checkNextSeq(seq); try { - if (doc.from === "guest") { - const qrDoc = await qrDb.get(`qr:${doc.qr_code_uri}`); + if (msgDoc.from === "guest") { + // send message to owner + const qrDoc = await qrDb.get(`qr_code:${msgDoc.qr_code_uri}`); if (qrDoc.messageDeliveryMethod !== "webPush") { console.log(`Skip !webPush`); continue; } - console.log("FROM GUEST"); - console.log(qrDoc.user_uuid); - console.log(qrDoc.browser_uuid); - } else if (doc.from === "owner") { + const { rows } = await webPushSubscriptionsDb.allDocs({ + startkey: `web_push_subscription:${qrDoc.user_uuid}:`, + endkey: `web_push_subscription:${qrDoc.user_uuid}:\uffff`, + include_docs: true, + }); + + if (rows.length === 0) { + console.log("NO SUBSCRIPTIONS"); + } + + for (const row of rows) { + const subscription = row.doc.subscription; + const topic = `chat-${msgDoc.qr_code_uri}-${msgDoc.chat}`; + const tag = `${topic}-${(new Date(msgDoc.created_at).getTime() - new Date(qrDoc.created_at).getTime()).toString(36)}`; + + const options: NotificationOptions = { + icon: "/images/qr-code-logo-200x200.jpg", + body: msgDoc.body, + tag, + requireInteraction: true, + data: { + url: `/manage/${msgDoc.qr_code_uri}/chat/${msgDoc.chat}`, + payload: { tag }, + }, + } as const; + + await webpush.sendNotification( + subscription, + JSON.stringify({ + type: "notification", + notification: { + title: qrDoc.name, + options, + }, + }), + { + urgency: "high", + topic, + }, + ); + + console.log("sent"); + } + } else if (msgDoc.from === "owner") { console.log("FROM OWNER"); } else { continue; } console.log( - `Processed change: ${JSON.stringify({ _id: change.id, ref: doc._rev, seq: change.seq })}`, + `Processed change: ${JSON.stringify({ _id: change.id, ref: msgDoc._rev, seq: change.seq })}`, ); } catch (error) { console.error( - `Failed processing: ${JSON.stringify(error)} change: ${JSON.stringify({ id: doc._id, rev: doc._rev, seq: seq })}`, + `Failed processing: ${JSON.stringify(error)} change: ${JSON.stringify({ id: msgDoc._id, rev: msgDoc._rev, seq: seq })}`, ); } finally { await serviceSeqTracker.saveSeq(seq); diff --git a/services/message-delivery-method-web-push/src/serviceSeqTracker.ts b/services/message-delivery-method-web-push/src/serviceSeqTracker.ts index 726489b..0d6b45d 100644 --- a/services/message-delivery-method-web-push/src/serviceSeqTracker.ts +++ b/services/message-delivery-method-web-push/src/serviceSeqTracker.ts @@ -16,7 +16,8 @@ export class ServiceSeqTracker { throw error; // Re-throw other errors }); - this.serviceDoc.lastSeq = 0; + // this.serviceDoc.lastSeq = + // "126-g1AAAACLeJzLYWBgYMpgTmHgzcvPy09JdcjLz8gvLskBCScyJNX___8_K4M50SUXKMCebGlpnmxpiq4Yh_Y8FiDJ0ACk_kNNsQKbYpSakphkYYKuJwsAb2YrPw"; return this.serviceDoc.lastSeq; }