Skip to content

Commit 54ff1b2

Browse files
Update handler
1 parent 9ceed3e commit 54ff1b2

File tree

1 file changed

+256
-0
lines changed

1 file changed

+256
-0
lines changed

src/handler.ts

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import {
2+
DeepClient,
3+
SerialOperation,
4+
Table,
5+
} from '@deep-foundation/deeplinks/imports/client';
6+
import { Link } from '@deep-foundation/deeplinks/imports/minilinks';
7+
import * as FirebaseAdmin from 'firebase-admin';
8+
9+
async ({
10+
deep,
11+
data: { newLink: notifyLink, triggeredByLinkId },
12+
}: {
13+
deep: DeepClient;
14+
data: { newLink: Link<number>, triggeredByLinkId: number };
15+
}) => {
16+
const {default: firebaseAdmin} = await import('firebase-admin');
17+
const util = await import('util');
18+
const { createSerialOperation } = await import('@deep-foundation/deeplinks/imports/gql/index.js')
19+
const logs: Array<any> = [];
20+
const DEFAULT_LOG_DEPTH = 3;
21+
22+
try {
23+
const result = await main();
24+
return {
25+
result,
26+
logs
27+
}
28+
} catch (error) {
29+
return {
30+
error,
31+
logs
32+
}
33+
}
34+
35+
async function main() {
36+
const log = getNamespacedLogger({ namespace: main.name });
37+
log({notifyLink})
38+
const notificationLinkId = notifyLink.from_id!;
39+
log({ notificationLinkId })
40+
const { data: [notificationLink] } = await deep.select(notificationLinkId)
41+
log({ notificationLink })
42+
if (!notificationLink.value?.value) {
43+
throw new Error(`##${notificationLinkId} must have value`)
44+
}
45+
const title = notificationLink.value.value.title;
46+
log({ title })
47+
if (!title) {
48+
throw new Error(`Object value of ##${notificationLinkId} must have title property`)
49+
}
50+
51+
const body = notificationLink.value.value.body;
52+
log({ body })
53+
if (!body) {
54+
throw new Error(`Object value of ##${notificationLinkId} must have body property`)
55+
}
56+
57+
const deviceLinkId = notifyLink.to_id!;
58+
log({ deviceLinkId })
59+
60+
const containTypeLinkId = await deep.id('@deep-foundation/core', 'Contain');
61+
log({ containTypeLinkId })
62+
63+
const serviceAccount = await getServiceAccount({
64+
containTypeLinkId,
65+
triggeredByLinkId,
66+
});
67+
log({ serviceAccount })
68+
69+
const firebaseApplication = await getFirebaseApplication({
70+
firebaseAdmin,
71+
serviceAccount,
72+
});
73+
log({ firebaseApplication })
74+
75+
const deviceRegistrationToken = await getDeviceRegistrationToken({
76+
containTypeLinkId,
77+
deviceLinkId,
78+
});
79+
log({ deviceRegistrationToken })
80+
81+
const pushNotificationData = {
82+
token: deviceRegistrationToken,
83+
notification: {
84+
title: title,
85+
body: body,
86+
},
87+
};
88+
log({ pushNotificationData })
89+
90+
await firebaseAdmin.messaging(firebaseApplication).send(pushNotificationData);
91+
await deep.insert({
92+
type_id: await deep.id(deep.linkId!, 'Notified'),
93+
in: {
94+
data: {
95+
type_id: containTypeLinkId,
96+
from_id: triggeredByLinkId,
97+
},
98+
},
99+
from_id: notifyLink.id,
100+
to_id: deviceLinkId,
101+
});
102+
103+
firebaseApplication.delete();
104+
}
105+
106+
async function getServiceAccount({ containTypeLinkId, triggeredByLinkId }: {containTypeLinkId: number, triggeredByLinkId: number}) {
107+
const log = getNamespacedLogger({ namespace: getServiceAccount.name });
108+
const serviceAccountTypeLinkId = await deep.id(
109+
deep.linkId!,
110+
'ServiceAccount'
111+
);
112+
log({ serviceAccountTypeLinkId })
113+
const usesServiceAccountTypeLinkId = await deep.id(
114+
deep.linkId!,
115+
'UsesServiceAccount'
116+
);
117+
log({ usesServiceAccountTypeLinkId })
118+
const selectData = {
119+
_or: [
120+
{
121+
type_id: serviceAccountTypeLinkId,
122+
in: {
123+
type_id: containTypeLinkId,
124+
from_id: triggeredByLinkId,
125+
},
126+
},
127+
{
128+
type_id: usesServiceAccountTypeLinkId,
129+
from_id: triggeredByLinkId,
130+
},
131+
],
132+
};
133+
const { data } = await deep.select(selectData);
134+
log({ data })
135+
if (data.length === 0) {
136+
throw new Error(
137+
`Select with data ${JSON.stringify(selectData)} returned empty result`
138+
);
139+
}
140+
let serviceAccountLink;
141+
const usesServiceAccountLinks = data.filter(
142+
(link) => link.type_id === usesServiceAccountTypeLinkId
143+
);
144+
if (usesServiceAccountLinks.length > 1) {
145+
throw new Error(
146+
`There must be only one link of type ${usesServiceAccountTypeLinkId} and from ${triggeredByLinkId}, instead there are ${usesServiceAccountLinks
147+
.map((link) => `##${link.id}`)
148+
.join(', ')}`
149+
);
150+
} else if (usesServiceAccountLinks.length === 1) {
151+
const usesServiceAccountLink = usesServiceAccountLinks[0];
152+
serviceAccountLink = data.find(
153+
(link) => link.id === usesServiceAccountLink.to_id
154+
);
155+
} else if (usesServiceAccountLinks.length === 0) {
156+
const serviceAccountLinks = data.filter(
157+
(link) => link.type_id === serviceAccountTypeLinkId
158+
);
159+
if (serviceAccountLinks.length > 1) {
160+
throw new Error(
161+
`There must be only one link of type ##${serviceAccountTypeLinkId} and contained by ##${triggeredByLinkId}, instead there are ${serviceAccountLinks
162+
.map((link) => `##${link.id}`)
163+
.join(', ')}`
164+
);
165+
} else if (serviceAccountLinks.length === 1) {
166+
serviceAccountLink = serviceAccountLinks[0];
167+
} else if (serviceAccountLinks.length === 0) {
168+
throw new Error(
169+
`A link of type ##${serviceAccountTypeLinkId} and contained by ##${triggeredByLinkId} is not found`
170+
);
171+
}
172+
}
173+
log({ serviceAccountLink })
174+
if (!serviceAccountLink) {
175+
throw new Error(
176+
`A link of type ##${usesServiceAccountTypeLinkId} and from ##${triggeredByLinkId} is not found`
177+
);
178+
}
179+
if (!serviceAccountLink.value?.value) {
180+
throw new Error(`##${serviceAccountLink.id} must have value`);
181+
}
182+
const result = serviceAccountLink.value.value;
183+
log({ result })
184+
return result;
185+
}
186+
187+
async function getDeviceRegistrationToken({
188+
containTypeLinkId,
189+
deviceLinkId,
190+
}: {
191+
containTypeLinkId: number;
192+
deviceLinkId: number;
193+
}) {
194+
const log = getNamespacedLogger({ namespace: getDeviceRegistrationToken.name });
195+
const deviceRegistrationTokenTypeLinkId = await deep.id(
196+
deep.linkId!,
197+
'DeviceRegistrationToken'
198+
);
199+
log({ deviceRegistrationTokenTypeLinkId })
200+
const selectData = {
201+
type_id: deviceRegistrationTokenTypeLinkId,
202+
in: {
203+
type_id: containTypeLinkId,
204+
from_id: deviceLinkId,
205+
},
206+
};
207+
log({ selectData })
208+
const {
209+
data: [deviceRegistrationTokenLink],
210+
} = await deep.select(selectData);
211+
if (!deviceRegistrationTokenLink) {
212+
throw new Error(
213+
`##${deviceLinkId} must have contained a link of type ##${deviceRegistrationTokenTypeLinkId}. Select with data ${JSON.stringify(
214+
selectData
215+
)} returned empty result`
216+
);
217+
}
218+
log({ deviceRegistrationTokenLink })
219+
if (!deviceRegistrationTokenLink.value?.value) {
220+
throw new Error(`##${deviceRegistrationTokenLink.id} must have value`);
221+
}
222+
const result = deviceRegistrationTokenLink.value.value;
223+
log({ result })
224+
return result;
225+
}
226+
227+
async function getFirebaseApplication(options: {
228+
firebaseAdmin: typeof FirebaseAdmin,
229+
serviceAccount: FirebaseAdmin.ServiceAccount,
230+
}): Promise<FirebaseAdmin.app.App> {
231+
const log = getNamespacedLogger({ namespace: getFirebaseApplication.name });
232+
log({ options })
233+
const {
234+
firebaseAdmin,
235+
serviceAccount,
236+
} = options;
237+
firebaseAdmin.apps.forEach((app) => app?.delete());
238+
return firebaseAdmin.initializeApp({
239+
credential: firebaseAdmin.credential.cert(serviceAccount),
240+
});
241+
}
242+
243+
244+
function getNamespacedLogger({
245+
namespace,
246+
depth = DEFAULT_LOG_DEPTH,
247+
}: {
248+
namespace: string;
249+
depth?: number;
250+
}) {
251+
return function (content: any) {
252+
const message = util.inspect(content, { depth });
253+
logs.push(`${namespace}: ${message}`);
254+
};
255+
}
256+
};

0 commit comments

Comments
 (0)