@things-factory/notification 8.0.87 → 8.0.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/fcm.d.ts +2 -2
- package/dist-server/controllers/fcm.js +80 -102
- package/dist-server/controllers/fcm.js.map +1 -1
- package/dist-server/routers/notification-router.js +4 -4
- package/dist-server/routers/notification-router.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -1
- package/dist-server/service/index.js +4 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/push-subscription/index.d.ts +3 -0
- package/dist-server/service/push-subscription/index.js +7 -0
- package/dist-server/service/push-subscription/index.js.map +1 -0
- package/dist-server/service/push-subscription/push-subscription.d.ts +17 -0
- package/dist-server/service/push-subscription/push-subscription.js +80 -0
- package/dist-server/service/push-subscription/push-subscription.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/server/controllers/fcm.ts +87 -111
- package/server/routers/notification-router.ts +4 -4
- package/server/service/index.ts +4 -1
- package/server/service/push-subscription/index.ts +4 -0
- package/server/service/push-subscription/push-subscription.ts +70 -0
|
@@ -15,10 +15,10 @@ export declare function sendNotification({ receivers, message }: {
|
|
|
15
15
|
receivers: any;
|
|
16
16
|
message: any;
|
|
17
17
|
}): Promise<void>;
|
|
18
|
-
export declare function register(user: any, { subscription }: {
|
|
18
|
+
export declare function register(domain: any, user: any, { subscription }: {
|
|
19
19
|
subscription: any;
|
|
20
20
|
}): Promise<boolean>;
|
|
21
|
-
export declare function unregister(user: any, { subscription }: {
|
|
21
|
+
export declare function unregister(domain: any, user: any, { subscription }: {
|
|
22
22
|
subscription: any;
|
|
23
23
|
}): Promise<boolean>;
|
|
24
24
|
export declare function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }: {
|
|
@@ -5,15 +5,22 @@ exports.sendNotification = sendNotification;
|
|
|
5
5
|
exports.register = register;
|
|
6
6
|
exports.unregister = unregister;
|
|
7
7
|
exports.notify = notify;
|
|
8
|
-
const
|
|
9
|
-
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
8
|
+
const typeorm_1 = require("typeorm");
|
|
10
9
|
const env_1 = require("@things-factory/env");
|
|
10
|
+
const shell_1 = require("@things-factory/shell");
|
|
11
11
|
const app_1 = require("firebase-admin/app");
|
|
12
12
|
const messaging_1 = require("firebase-admin/messaging");
|
|
13
|
+
const push_subscription_1 = require("../service/push-subscription/push-subscription");
|
|
13
14
|
const notificationConfig = env_1.config.get('notification') || {};
|
|
14
15
|
const { fcm, vapidKey } = notificationConfig;
|
|
15
|
-
const { serviceAccount, appConfig
|
|
16
|
+
const { serviceAccount, appConfig } = fcm || {};
|
|
16
17
|
const { publicKey } = vapidKey || {};
|
|
18
|
+
// FCM 전송 시 만료/무효로 판정되어 DB에서 정리할 토큰 에러 코드
|
|
19
|
+
const INVALID_TOKEN_CODES = [
|
|
20
|
+
'messaging/registration-token-not-registered',
|
|
21
|
+
'messaging/invalid-registration-token',
|
|
22
|
+
'messaging/invalid-argument'
|
|
23
|
+
];
|
|
17
24
|
var messaging;
|
|
18
25
|
if (serviceAccount) {
|
|
19
26
|
try {
|
|
@@ -47,95 +54,96 @@ async function sendNotification({ receivers, message }) {
|
|
|
47
54
|
}
|
|
48
55
|
notify(Object.assign({ receivers }, message));
|
|
49
56
|
}
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
// 푸시 구독 등록: FCM 토큰을 DB에 저장 (device-group 대체). (domain, token) 기준 upsert
|
|
58
|
+
async function register(domain, user, { subscription }) {
|
|
59
|
+
if (!messaging || !subscription || !user || !domain) {
|
|
52
60
|
return false;
|
|
53
61
|
}
|
|
54
62
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
registration_ids: [subscription]
|
|
61
|
-
});
|
|
63
|
+
const repository = (0, shell_1.getRepository)(push_subscription_1.PushSubscription);
|
|
64
|
+
const existing = await repository.findOneBy({ domain: { id: domain.id }, token: subscription });
|
|
65
|
+
if (existing) {
|
|
66
|
+
// 재구독/기기 재할당: 소유 user만 갱신
|
|
67
|
+
await repository.save(Object.assign(Object.assign({}, existing), { user: { id: user.id }, updater: { id: user.id } }));
|
|
62
68
|
}
|
|
63
69
|
else {
|
|
64
|
-
await
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
await repository.save({
|
|
71
|
+
domain: { id: domain.id },
|
|
72
|
+
user: { id: user.id },
|
|
73
|
+
token: subscription,
|
|
74
|
+
creator: { id: user.id },
|
|
75
|
+
updater: { id: user.id }
|
|
69
76
|
});
|
|
70
77
|
}
|
|
71
78
|
return true;
|
|
72
79
|
}
|
|
73
80
|
catch (err) {
|
|
74
|
-
|
|
81
|
+
env_1.logger.error('[FCM] register 실패', err);
|
|
82
|
+
return false;
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
// 푸시 구독 해제: 해당 토큰 행을 하드 삭제
|
|
86
|
+
async function unregister(domain, user, { subscription }) {
|
|
87
|
+
if (!messaging || !subscription || !user || !domain) {
|
|
79
88
|
return false;
|
|
80
89
|
}
|
|
81
90
|
try {
|
|
82
|
-
|
|
83
|
-
if (notification_key) {
|
|
84
|
-
await postDeviceGroup({
|
|
85
|
-
operation: 'remove',
|
|
86
|
-
notification_key_name: user.id,
|
|
87
|
-
notification_key,
|
|
88
|
-
registration_ids: [subscription]
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
+
await (0, shell_1.getRepository)(push_subscription_1.PushSubscription).delete({ domain: { id: domain.id }, token: subscription });
|
|
91
92
|
return true;
|
|
92
93
|
}
|
|
93
94
|
catch (err) {
|
|
95
|
+
env_1.logger.error('[FCM] unregister 실패', err);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 토큰 배열로 FCM HTTP v1 멀티캐스트 전송 + 무효 토큰 DB 정리
|
|
100
|
+
async function sendToTokens(tokens, notification, data) {
|
|
101
|
+
if (!tokens || tokens.length === 0) {
|
|
94
102
|
return;
|
|
95
103
|
}
|
|
104
|
+
// sendEachForMulticast 는 1회 최대 500토큰. 초과 시 청크 (실제 초과 도메인 생기면 대응)
|
|
105
|
+
const response = await messaging.sendEachForMulticast({ notification, data, tokens });
|
|
106
|
+
env_1.logger.info(`[FCM] sent ${response.successCount}/${tokens.length}, failed ${response.failureCount}`);
|
|
107
|
+
if (response.failureCount > 0) {
|
|
108
|
+
const deadTokens = [];
|
|
109
|
+
response.responses.forEach((resp, idx) => {
|
|
110
|
+
if (!resp.success) {
|
|
111
|
+
const code = resp.error && resp.error.code;
|
|
112
|
+
if (INVALID_TOKEN_CODES.includes(code)) {
|
|
113
|
+
deadTokens.push(tokens[idx]);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// 일시적 오류 등 → 토큰 보존, 로그만
|
|
117
|
+
env_1.logger.error('[FCM] send failure (token kept):', code, resp.error && resp.error.message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if (deadTokens.length > 0) {
|
|
122
|
+
await (0, shell_1.getRepository)(push_subscription_1.PushSubscription).delete({ token: (0, typeorm_1.In)(deadTokens) });
|
|
123
|
+
env_1.logger.warn(`[FCM] pruned ${deadTokens.length} invalid token(s)`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return response;
|
|
96
127
|
}
|
|
97
128
|
async function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }) {
|
|
98
129
|
if (!messaging) {
|
|
130
|
+
env_1.logger.error('[FCM] messaging 미초기화 → 전송 중단 (serviceAccount/FCM_PRIVATE_KEY 확인)');
|
|
99
131
|
return;
|
|
100
132
|
}
|
|
101
|
-
// Caution: non-string attributes are not allowed in FCM payload validation
|
|
133
|
+
// Caution: non-string attributes are not allowed in FCM payload validation.
|
|
134
|
+
// image은 빈 문자열이면 HTTP v1이 "invalid URL"로 거부하므로 값이 있을 때만 포함
|
|
102
135
|
var notification = {
|
|
103
136
|
title: title || '',
|
|
104
|
-
body: body || ''
|
|
105
|
-
image: image || ''
|
|
137
|
+
body: body || ''
|
|
106
138
|
};
|
|
139
|
+
if (image) {
|
|
140
|
+
notification.image = image;
|
|
141
|
+
}
|
|
142
|
+
// 1) 명시적 토큰 전송
|
|
107
143
|
if (tokens && tokens instanceof Array && tokens.length > 0) {
|
|
108
|
-
|
|
109
|
-
const response = await messaging.sendMulticast({
|
|
110
|
-
notification,
|
|
111
|
-
data,
|
|
112
|
-
tokens
|
|
113
|
-
});
|
|
114
|
-
if (response.failureCount > 0) {
|
|
115
|
-
const failedTokens = [];
|
|
116
|
-
response.responses.forEach((resp, idx) => {
|
|
117
|
-
if (!resp.success) {
|
|
118
|
-
failedTokens.push(tokens[idx]);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
env_1.logger.error('List of tokens that caused failures: ' + failedTokens);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
const token = tokens[0];
|
|
126
|
-
try {
|
|
127
|
-
const response = await messaging.send({
|
|
128
|
-
notification,
|
|
129
|
-
data,
|
|
130
|
-
token
|
|
131
|
-
});
|
|
132
|
-
env_1.logger.log('Successfully sent message:', response);
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
env_1.logger.error('Error sending message:', err);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
144
|
+
await sendToTokens(tokens, notification, data);
|
|
138
145
|
}
|
|
146
|
+
// 2) 토픽 전송
|
|
139
147
|
if (topic) {
|
|
140
148
|
try {
|
|
141
149
|
const response = await messaging.send({
|
|
@@ -149,48 +157,18 @@ async function notify({ receivers, privileges, tokens, topic, title, body, data,
|
|
|
149
157
|
env_1.logger.error('Error sending message:', err);
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
160
|
+
// 3) 수신자(user.id 배열) → 저장된 구독 토큰 조회 후 전송
|
|
161
|
+
if (receivers && receivers instanceof Array && receivers.length > 0) {
|
|
162
|
+
const rows = await (0, shell_1.getRepository)(push_subscription_1.PushSubscription).find({
|
|
163
|
+
where: { user: { id: (0, typeorm_1.In)(receivers) } }
|
|
164
|
+
});
|
|
165
|
+
const receiverTokens = [...new Set(rows.map(r => r.token))];
|
|
166
|
+
if (receiverTokens.length === 0) {
|
|
167
|
+
env_1.logger.warn('[FCM] no push subscriptions for receivers:', JSON.stringify(receivers));
|
|
161
168
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
async function postDeviceGroup(body) {
|
|
165
|
-
var response = await (0, node_fetch_1.default)('https://fcm.googleapis.com/fcm/notification', {
|
|
166
|
-
method: 'POST',
|
|
167
|
-
headers: {
|
|
168
|
-
'Content-Type': 'application/json',
|
|
169
|
-
Authorization: `key=${serverKey}`,
|
|
170
|
-
project_id: appConfig.messagingSenderId
|
|
171
|
-
},
|
|
172
|
-
body: JSON.stringify(body)
|
|
173
|
-
});
|
|
174
|
-
if (response.ok) {
|
|
175
|
-
const { notification_key } = await response.json();
|
|
176
|
-
return notification_key;
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
console.error('postDeviceGroup-notok', response.status, await response.text());
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
async function getDeviceGroup(notificationKeyName) {
|
|
183
|
-
const response = await (0, node_fetch_1.default)(`https://fcm.googleapis.com/fcm/notification?notification_key_name=${notificationKeyName}`, {
|
|
184
|
-
method: 'GET',
|
|
185
|
-
headers: {
|
|
186
|
-
'Content-Type': 'application/json',
|
|
187
|
-
Authorization: `key=${serverKey}`,
|
|
188
|
-
project_id: appConfig.messagingSenderId
|
|
169
|
+
else {
|
|
170
|
+
await sendToTokens(receiverTokens, notification, data);
|
|
189
171
|
}
|
|
190
|
-
});
|
|
191
|
-
if (response.ok) {
|
|
192
|
-
const { notification_key } = await response.json();
|
|
193
|
-
return notification_key;
|
|
194
172
|
}
|
|
195
173
|
}
|
|
196
174
|
//# sourceMappingURL=fcm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fcm.js","sourceRoot":"","sources":["../../server/controllers/fcm.ts"],"names":[],"mappings":"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","sourcesContent":["import fetch from 'node-fetch'\n\nimport { config, logger } from '@things-factory/env'\n\nimport { initializeApp, cert } from 'firebase-admin/app'\nimport { getMessaging } from 'firebase-admin/messaging'\n\nconst notificationConfig = config.get('notification') || {}\nconst { fcm, vapidKey } = notificationConfig\nconst { serviceAccount, appConfig, serverKey } = fcm || {}\nconst { publicKey } = vapidKey || {}\n\nvar messaging\n\nif (serviceAccount) {\n try {\n const app = initializeApp({\n credential: cert(serviceAccount)\n })\n\n messaging = getMessaging(app)\n } catch (err) {\n logger.error('incorrect notification configuration')\n logger.error(err)\n }\n}\n\nexport function getConfig() {\n return messaging\n ? {\n appConfig,\n vapidPublicKey: publicKey\n }\n : {}\n}\n\n/**\n * sendNotification\n *\n * @param receiver user.id who will receive notification\n * @param message message object to be sent\n */\nexport async function sendNotification({ receivers, message }) {\n if (!messaging) {\n return\n }\n\n notify({\n receivers,\n ...message\n })\n}\n\nexport async function register(user, { subscription }) {\n if (!messaging || !subscription || !user) {\n return false\n }\n\n try {\n var notification_key = await getDeviceGroup(user.id)\n\n if (!notification_key) {\n await postDeviceGroup({\n operation: 'create',\n notification_key_name: user.id,\n registration_ids: [subscription]\n })\n } else {\n await postDeviceGroup({\n operation: 'add',\n notification_key_name: user.id,\n notification_key,\n registration_ids: [subscription]\n })\n }\n\n return true\n } catch (err) {\n return\n }\n}\n\nexport async function unregister(user, { subscription }) {\n if (!messaging || !subscription || !user) {\n return false\n }\n\n try {\n var notification_key = await getDeviceGroup(user.id)\n\n if (notification_key) {\n await postDeviceGroup({\n operation: 'remove',\n notification_key_name: user.id,\n notification_key,\n registration_ids: [subscription]\n })\n }\n\n return true\n } catch (err) {\n return\n }\n}\n\nexport async function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }) {\n if (!messaging) {\n return\n }\n\n // Caution: non-string attributes are not allowed in FCM payload validation\n var notification = {\n title: title || '',\n body: body || '',\n image: image || ''\n }\n\n if (tokens && tokens instanceof Array && tokens.length > 0) {\n if (tokens.length > 1) {\n const response = await messaging.sendMulticast({\n notification,\n data,\n tokens\n })\n\n if (response.failureCount > 0) {\n const failedTokens = []\n response.responses.forEach((resp, idx) => {\n if (!resp.success) {\n failedTokens.push(tokens[idx])\n }\n })\n logger.error('List of tokens that caused failures: ' + failedTokens)\n }\n } else {\n const token = tokens[0]\n\n try {\n const response = await messaging.send({\n notification,\n data,\n token\n })\n logger.log('Successfully sent message:', response)\n } catch (err) {\n logger.error('Error sending message:', err)\n }\n }\n }\n\n if (topic) {\n try {\n const response = await messaging.send({\n notification,\n data,\n topic\n })\n logger.log('Successfully sent message:', response)\n } catch (err) {\n logger.error('Error sending message:', err)\n }\n }\n\n if (receivers && receivers instanceof Array) {\n for (let receiver of receivers) {\n const notification_key = await getDeviceGroup(receiver)\n\n if (notification_key) {\n await messaging.sendToDeviceGroup(notification_key, {\n notification,\n data\n })\n }\n }\n }\n}\n\nasync function postDeviceGroup(body) {\n var response = await fetch('https://fcm.googleapis.com/fcm/notification', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `key=${serverKey}`,\n project_id: appConfig.messagingSenderId\n },\n body: JSON.stringify(body)\n })\n\n if (response.ok) {\n const { notification_key } = await response.json()\n return notification_key\n } else {\n console.error('postDeviceGroup-notok', response.status, await response.text())\n }\n}\n\nasync function getDeviceGroup(notificationKeyName) {\n const response = await fetch(\n `https://fcm.googleapis.com/fcm/notification?notification_key_name=${notificationKeyName}`,\n {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `key=${serverKey}`,\n project_id: appConfig.messagingSenderId\n }\n }\n )\n\n if (response.ok) {\n const { notification_key } = await response.json()\n return notification_key\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fcm.js","sourceRoot":"","sources":["../../server/controllers/fcm.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\n\nimport { config, logger } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport { initializeApp, cert } from 'firebase-admin/app'\nimport { getMessaging } from 'firebase-admin/messaging'\n\nimport { PushSubscription } from '../service/push-subscription/push-subscription'\n\nconst notificationConfig = config.get('notification') || {}\nconst { fcm, vapidKey } = notificationConfig\nconst { serviceAccount, appConfig } = fcm || {}\nconst { publicKey } = vapidKey || {}\n\n// FCM 전송 시 만료/무효로 판정되어 DB에서 정리할 토큰 에러 코드\nconst INVALID_TOKEN_CODES = [\n 'messaging/registration-token-not-registered',\n 'messaging/invalid-registration-token',\n 'messaging/invalid-argument'\n]\n\nvar messaging\n\nif (serviceAccount) {\n try {\n const app = initializeApp({\n credential: cert(serviceAccount)\n })\n\n messaging = getMessaging(app)\n } catch (err) {\n logger.error('incorrect notification configuration')\n logger.error(err)\n }\n}\n\nexport function getConfig() {\n return messaging\n ? {\n appConfig,\n vapidPublicKey: publicKey\n }\n : {}\n}\n\n/**\n * sendNotification\n *\n * @param receiver user.id who will receive notification\n * @param message message object to be sent\n */\nexport async function sendNotification({ receivers, message }) {\n if (!messaging) {\n return\n }\n\n notify({\n receivers,\n ...message\n })\n}\n\n// 푸시 구독 등록: FCM 토큰을 DB에 저장 (device-group 대체). (domain, token) 기준 upsert\nexport async function register(domain, user, { subscription }) {\n if (!messaging || !subscription || !user || !domain) {\n return false\n }\n\n try {\n const repository = getRepository(PushSubscription)\n const existing = await repository.findOneBy({ domain: { id: domain.id }, token: subscription })\n\n if (existing) {\n // 재구독/기기 재할당: 소유 user만 갱신\n await repository.save({ ...existing, user: { id: user.id }, updater: { id: user.id } })\n } else {\n await repository.save({\n domain: { id: domain.id },\n user: { id: user.id },\n token: subscription,\n creator: { id: user.id },\n updater: { id: user.id }\n })\n }\n\n return true\n } catch (err) {\n logger.error('[FCM] register 실패', err)\n return false\n }\n}\n\n// 푸시 구독 해제: 해당 토큰 행을 하드 삭제\nexport async function unregister(domain, user, { subscription }) {\n if (!messaging || !subscription || !user || !domain) {\n return false\n }\n\n try {\n await getRepository(PushSubscription).delete({ domain: { id: domain.id }, token: subscription })\n return true\n } catch (err) {\n logger.error('[FCM] unregister 실패', err)\n return false\n }\n}\n\n// 토큰 배열로 FCM HTTP v1 멀티캐스트 전송 + 무효 토큰 DB 정리\nasync function sendToTokens(tokens, notification, data) {\n if (!tokens || tokens.length === 0) {\n return\n }\n\n // sendEachForMulticast 는 1회 최대 500토큰. 초과 시 청크 (실제 초과 도메인 생기면 대응)\n const response = await messaging.sendEachForMulticast({ notification, data, tokens })\n logger.info(`[FCM] sent ${response.successCount}/${tokens.length}, failed ${response.failureCount}`)\n\n if (response.failureCount > 0) {\n const deadTokens = []\n response.responses.forEach((resp, idx) => {\n if (!resp.success) {\n const code = resp.error && resp.error.code\n if (INVALID_TOKEN_CODES.includes(code)) {\n deadTokens.push(tokens[idx])\n } else {\n // 일시적 오류 등 → 토큰 보존, 로그만\n logger.error('[FCM] send failure (token kept):', code, resp.error && resp.error.message)\n }\n }\n })\n\n if (deadTokens.length > 0) {\n await getRepository(PushSubscription).delete({ token: In(deadTokens) })\n logger.warn(`[FCM] pruned ${deadTokens.length} invalid token(s)`)\n }\n }\n\n return response\n}\n\nexport async function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }) {\n if (!messaging) {\n logger.error('[FCM] messaging 미초기화 → 전송 중단 (serviceAccount/FCM_PRIVATE_KEY 확인)')\n return\n }\n\n // Caution: non-string attributes are not allowed in FCM payload validation.\n // image은 빈 문자열이면 HTTP v1이 \"invalid URL\"로 거부하므로 값이 있을 때만 포함\n var notification: { title: string; body: string; image?: string } = {\n title: title || '',\n body: body || ''\n }\n if (image) {\n notification.image = image\n }\n\n // 1) 명시적 토큰 전송\n if (tokens && tokens instanceof Array && tokens.length > 0) {\n await sendToTokens(tokens, notification, data)\n }\n\n // 2) 토픽 전송\n if (topic) {\n try {\n const response = await messaging.send({\n notification,\n data,\n topic\n })\n logger.log('Successfully sent message:', response)\n } catch (err) {\n logger.error('Error sending message:', err)\n }\n }\n\n // 3) 수신자(user.id 배열) → 저장된 구독 토큰 조회 후 전송\n if (receivers && receivers instanceof Array && receivers.length > 0) {\n const rows = await getRepository(PushSubscription).find({\n where: { user: { id: In(receivers) } }\n })\n const receiverTokens = [...new Set(rows.map(r => r.token))]\n\n if (receiverTokens.length === 0) {\n logger.warn('[FCM] no push subscriptions for receivers:', JSON.stringify(receivers))\n } else {\n await sendToTokens(receiverTokens, notification, data)\n }\n }\n}\n"]}
|
|
@@ -17,15 +17,15 @@ exports.notificationRouter.get('/config', async (context, next) => {
|
|
|
17
17
|
context.body = config;
|
|
18
18
|
});
|
|
19
19
|
exports.notificationRouter.post('/register', async (context, next) => {
|
|
20
|
-
const { user } = context.state;
|
|
20
|
+
const { user, domain } = context.state;
|
|
21
21
|
const { body } = context.request;
|
|
22
|
-
context.body = await (0, fcm_1.register)(user, body);
|
|
22
|
+
context.body = await (0, fcm_1.register)(domain, user, body);
|
|
23
23
|
context.status = 200;
|
|
24
24
|
});
|
|
25
25
|
exports.notificationRouter.post('/unregister', async (context, next) => {
|
|
26
|
-
const { user } = context.state;
|
|
26
|
+
const { user, domain } = context.state;
|
|
27
27
|
const { body } = context.request;
|
|
28
|
-
context.body = await (0, fcm_1.unregister)(user, body);
|
|
28
|
+
context.body = await (0, fcm_1.unregister)(domain, user, body);
|
|
29
29
|
context.status = 200;
|
|
30
30
|
});
|
|
31
31
|
exports.notificationRouter.post('/notify', async (context, next) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-router.js","sourceRoot":"","sources":["../../server/routers/notification-router.ts"],"names":[],"mappings":";;;;AAAA,oEAA+B;AAC/B,qCAA+B;AAE/B,yDAAgD;AAChD,iDAAqD;AAErD,4CAAoE;AAEpE,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,iDAAiD,CAAC,CAAA;AAEpE,QAAA,kBAAkB,GAAG,IAAI,oBAAM,CAAC;IAC3C,MAAM,EAAE,eAAe;CACxB,CAAC,CAAA;AAEF,0BAAkB,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IACxD,MAAM,MAAM,GAAG,IAAA,eAAS,GAAE,CAAA;IAE1B,OAAO,CAAC,IAAI,GAAG,kBAAkB,CAAA;IACjC,OAAO,CAAC,IAAI,GAAG,MAAM,CAAA;AACvB,CAAC,CAAC,CAAA;AAEF,0BAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IAC3D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"notification-router.js","sourceRoot":"","sources":["../../server/routers/notification-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\nimport { ILike } from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getConfig, register, unregister } from '../controllers/fcm'\n\nconst debug = require('debug')('things-factory:notification:notification-router')\n\nexport const notificationRouter = new Router({\n prefix: '/notification'\n})\n\nnotificationRouter.get('/config', async (context, next) => {\n const config = getConfig()\n\n context.type = 'application/json'\n context.body = config\n})\n\nnotificationRouter.post('/register', async (context, next) => {\n const { user, domain } = context.state\n const { body } = context.request\n context.body = await register(domain, user, body)\n context.status = 200\n})\n\nnotificationRouter.post('/unregister', async (context, next) => {\n const { user, domain } = context.state\n const { body } = context.request\n context.body = await unregister(domain, user, body)\n context.status = 200\n})\n\nnotificationRouter.post('/notify', async (context, next) => {\n const { user, domain, notify } = context.state\n const repository = getRepository(User)\n\n var { receivers, ...options } = context.request.body\n\n debug('post:/notify', receivers, receivers.split(','), options)\n\n // TODO filter only users having current domain privilege\n receivers = (\n await Promise.all(\n receivers\n .split(',')\n .map(email => email.trim())\n .map(async email => {\n var receiver: User = await repository.findOneBy({ email: ILike(email) })\n return receiver && receiver.id\n })\n )\n ).filter(receiver => !!receiver)\n\n debug('post:/notify', receivers, user.id)\n\n await notify({\n receivers: receivers.length > 0 ? receivers : [user.id],\n ...options\n })\n\n context.body = {\n success: true\n }\n})\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './notification-rule/notification-rule';
|
|
2
2
|
export * from './notification/notification';
|
|
3
|
-
export
|
|
3
|
+
export * from './push-subscription/push-subscription';
|
|
4
|
+
export declare const entities: (typeof import("./notification-rule/notification-rule").NotificationRule | typeof import("./notification/notification").Notification | typeof import("./push-subscription/push-subscription").PushSubscription)[];
|
|
4
5
|
export declare const subscribers: typeof import("./notification-rule/event-subscriber").NotificationRuleHistoryEntitySubscriber[];
|
|
5
6
|
export declare const schema: {
|
|
6
7
|
typeDefs: {
|
|
@@ -5,13 +5,16 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
/* EXPORT ENTITY TYPES */
|
|
6
6
|
tslib_1.__exportStar(require("./notification-rule/notification-rule"), exports);
|
|
7
7
|
tslib_1.__exportStar(require("./notification/notification"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./push-subscription/push-subscription"), exports);
|
|
8
9
|
/* IMPORT ENTITIES AND RESOLVERS */
|
|
9
10
|
const notification_rule_1 = require("./notification-rule");
|
|
10
11
|
const notification_1 = require("./notification");
|
|
12
|
+
const push_subscription_1 = require("./push-subscription");
|
|
11
13
|
exports.entities = [
|
|
12
14
|
/* ENTITIES */
|
|
13
15
|
...notification_rule_1.entities,
|
|
14
|
-
...notification_1.entities
|
|
16
|
+
...notification_1.entities,
|
|
17
|
+
...push_subscription_1.entities
|
|
15
18
|
];
|
|
16
19
|
exports.subscribers = [
|
|
17
20
|
/* SUBSCRIBERS */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,gFAAqD;AACrD,sEAA2C;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,gFAAqD;AACrD,sEAA2C;AAC3C,gFAAqD;AAErD,mCAAmC;AACnC,2DAI4B;AAC5B,iDAKuB;AACvB,2DAA0E;AAE7D,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,4BAAwB;IAC3B,GAAG,uBAAoB;IACvB,GAAG,4BAAwB;CAC5B,CAAA;AAEY,QAAA,WAAW,GAAG;IACzB,iBAAiB;IACjB,GAAG,+BAA2B;CAC/B,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,QAAQ,oBACH,uBAAoB,CACxB;IACD,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,6BAAyB;QAC5B,GAAG,wBAAqB;QACxB,GAAG,wBAAqB;KACzB;IACD,UAAU,oBACL,yBAAsB,CAC1B;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './notification-rule/notification-rule'\nexport * from './notification/notification'\nexport * from './push-subscription/push-subscription'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport {\n entities as NotificationRuleEntities,\n resolvers as NotificationRuleResolvers,\n subscribers as NotificationRuleSubscribers\n} from './notification-rule'\nimport {\n entities as NotificationEntities,\n typeDefs as NotificationTypeDefs,\n resolvers as NotificationResolvers,\n directives as NotificationDirectives\n} from './notification'\nimport { entities as PushSubscriptionEntities } from './push-subscription'\n\nexport const entities = [\n /* ENTITIES */\n ...NotificationRuleEntities,\n ...NotificationEntities,\n ...PushSubscriptionEntities\n]\n\nexport const subscribers = [\n /* SUBSCRIBERS */\n ...NotificationRuleSubscribers\n]\n\nexport const schema = {\n typeDefs: {\n ...NotificationTypeDefs\n },\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...NotificationRuleResolvers,\n ...NotificationResolvers,\n ...NotificationResolvers\n ],\n directives: {\n ...NotificationDirectives\n }\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvers = exports.entities = void 0;
|
|
4
|
+
const push_subscription_1 = require("./push-subscription");
|
|
5
|
+
exports.entities = [push_subscription_1.PushSubscription];
|
|
6
|
+
exports.resolvers = [];
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/push-subscription/index.ts"],"names":[],"mappings":";;;AAAA,2DAAsD;AAEzC,QAAA,QAAQ,GAAG,CAAC,oCAAgB,CAAC,CAAA;AAC7B,QAAA,SAAS,GAAG,EAAE,CAAA","sourcesContent":["import { PushSubscription } from './push-subscription'\n\nexport const entities = [PushSubscription]\nexport const resolvers = []\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
|
2
|
+
import { User } from '@things-factory/auth-base';
|
|
3
|
+
export declare class PushSubscription {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
domain?: Domain;
|
|
6
|
+
domainId?: string;
|
|
7
|
+
user?: User;
|
|
8
|
+
userId?: string;
|
|
9
|
+
token: string;
|
|
10
|
+
createdAt?: Date;
|
|
11
|
+
updatedAt?: Date;
|
|
12
|
+
deletedAt?: Date;
|
|
13
|
+
creator?: User;
|
|
14
|
+
creatorId?: string;
|
|
15
|
+
updater?: User;
|
|
16
|
+
updaterId?: string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PushSubscription = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const typeorm_1 = require("typeorm");
|
|
6
|
+
const type_graphql_1 = require("type-graphql");
|
|
7
|
+
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
9
|
+
// FCM 웹 푸시 구독 토큰 저장 엔티티 (레거시 device-group 대체, HTTP v1 토큰 직접 전송용)
|
|
10
|
+
let PushSubscription = class PushSubscription {
|
|
11
|
+
};
|
|
12
|
+
exports.PushSubscription = PushSubscription;
|
|
13
|
+
tslib_1.__decorate([
|
|
14
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
15
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID),
|
|
16
|
+
tslib_1.__metadata("design:type", String)
|
|
17
|
+
], PushSubscription.prototype, "id", void 0);
|
|
18
|
+
tslib_1.__decorate([
|
|
19
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
20
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain),
|
|
21
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
22
|
+
], PushSubscription.prototype, "domain", void 0);
|
|
23
|
+
tslib_1.__decorate([
|
|
24
|
+
(0, typeorm_1.RelationId)((ps) => ps.domain),
|
|
25
|
+
tslib_1.__metadata("design:type", String)
|
|
26
|
+
], PushSubscription.prototype, "domainId", void 0);
|
|
27
|
+
tslib_1.__decorate([
|
|
28
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
29
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
|
|
30
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
31
|
+
], PushSubscription.prototype, "user", void 0);
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, typeorm_1.RelationId)((ps) => ps.user),
|
|
34
|
+
tslib_1.__metadata("design:type", String)
|
|
35
|
+
], PushSubscription.prototype, "userId", void 0);
|
|
36
|
+
tslib_1.__decorate([
|
|
37
|
+
(0, typeorm_1.Column)(),
|
|
38
|
+
(0, type_graphql_1.Field)(),
|
|
39
|
+
tslib_1.__metadata("design:type", String)
|
|
40
|
+
], PushSubscription.prototype, "token", void 0);
|
|
41
|
+
tslib_1.__decorate([
|
|
42
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
43
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
44
|
+
tslib_1.__metadata("design:type", Date)
|
|
45
|
+
], PushSubscription.prototype, "createdAt", void 0);
|
|
46
|
+
tslib_1.__decorate([
|
|
47
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
48
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
49
|
+
tslib_1.__metadata("design:type", Date)
|
|
50
|
+
], PushSubscription.prototype, "updatedAt", void 0);
|
|
51
|
+
tslib_1.__decorate([
|
|
52
|
+
(0, typeorm_1.DeleteDateColumn)(),
|
|
53
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
54
|
+
tslib_1.__metadata("design:type", Date)
|
|
55
|
+
], PushSubscription.prototype, "deletedAt", void 0);
|
|
56
|
+
tslib_1.__decorate([
|
|
57
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
58
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
|
|
59
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
60
|
+
], PushSubscription.prototype, "creator", void 0);
|
|
61
|
+
tslib_1.__decorate([
|
|
62
|
+
(0, typeorm_1.RelationId)((ps) => ps.creator),
|
|
63
|
+
tslib_1.__metadata("design:type", String)
|
|
64
|
+
], PushSubscription.prototype, "creatorId", void 0);
|
|
65
|
+
tslib_1.__decorate([
|
|
66
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
67
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
|
|
68
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
69
|
+
], PushSubscription.prototype, "updater", void 0);
|
|
70
|
+
tslib_1.__decorate([
|
|
71
|
+
(0, typeorm_1.RelationId)((ps) => ps.updater),
|
|
72
|
+
tslib_1.__metadata("design:type", String)
|
|
73
|
+
], PushSubscription.prototype, "updaterId", void 0);
|
|
74
|
+
exports.PushSubscription = PushSubscription = tslib_1.__decorate([
|
|
75
|
+
(0, typeorm_1.Entity)(),
|
|
76
|
+
(0, typeorm_1.Index)('ix_push_subscription_0', (ps) => [ps.domain, ps.token], { unique: true }),
|
|
77
|
+
(0, typeorm_1.Index)('ix_push_subscription_1', (ps) => [ps.user], { unique: false }),
|
|
78
|
+
(0, type_graphql_1.ObjectType)({ description: 'Entity for PushSubscription (FCM registration token)' })
|
|
79
|
+
], PushSubscription);
|
|
80
|
+
//# sourceMappingURL=push-subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-subscription.js","sourceRoot":"","sources":["../../../server/service/push-subscription/push-subscription.ts"],"names":[],"mappings":";;;;AAAA,qCAUgB;AAChB,+CAAoD;AAEpD,iDAA8C;AAC9C,yDAAgD;AAEhD,iEAAiE;AAK1D,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAgD5B,CAAA;AAhDY,4CAAgB;AAGlB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,CAAC;;4CACC;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;sCACb,cAAM;gDAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC;;kDAC/B;AAIjB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACjC,gBAAI;8CAAA;AAGX;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC;;gDAC/B;AAIf;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,GAAE;;+CACK;AAIb;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;mDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;mDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;mDAAA;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;iDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC;;mDAC/B;AAIlB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;iDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC;;mDAC/B;2BA/CP,gBAAgB;IAJ5B,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,wBAAwB,EAAE,CAAC,EAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAClG,IAAA,eAAK,EAAC,wBAAwB,EAAE,CAAC,EAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACvF,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,sDAAsD,EAAE,CAAC;GACvE,gBAAgB,CAgD5B","sourcesContent":["import {\n CreateDateColumn,\n UpdateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n Column,\n RelationId,\n ManyToOne,\n PrimaryGeneratedColumn\n} from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n// FCM 웹 푸시 구독 토큰 저장 엔티티 (레거시 device-group 대체, HTTP v1 토큰 직접 전송용)\n@Entity()\n@Index('ix_push_subscription_0', (ps: PushSubscription) => [ps.domain, ps.token], { unique: true })\n@Index('ix_push_subscription_1', (ps: PushSubscription) => [ps.user], { unique: false })\n@ObjectType({ description: 'Entity for PushSubscription (FCM registration token)' })\nexport class PushSubscription {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((ps: PushSubscription) => ps.domain)\n domainId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n user?: User\n\n @RelationId((ps: PushSubscription) => ps.user)\n userId?: string\n\n @Column()\n @Field()\n token: string // FCM registration token\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((ps: PushSubscription) => ps.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((ps: PushSubscription) => ps.updater)\n updaterId?: string\n}\n"]}
|