-
-
Notifications
You must be signed in to change notification settings - Fork 390
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: refactor send message to a cloud function (#3648)
- Loading branch information
1 parent
b251a4f
commit 8fa50e0
Showing
15 changed files
with
186 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export const createDoc = (data: object) => { | ||
return { | ||
...data, | ||
_created: new Date().toISOString(), | ||
_modified: new Date().toISOString(), | ||
_deleted: false, | ||
_id: _generateDocID(), | ||
} | ||
} | ||
|
||
const _generateDocID = () => { | ||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' | ||
let autoId = '' | ||
for (let i = 0; i < 20; i++) { | ||
autoId += chars.charAt(Math.floor(Math.random() * chars.length)) | ||
} | ||
return autoId | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { handleSendMessage } from './messages' // Path to your cloud function file | ||
import { SendMessage } from 'oa-shared' | ||
import * as functions from 'firebase-functions' | ||
|
||
const defaultData: SendMessage = { | ||
to: 'to@email.com', | ||
message: 'test message', | ||
name: 'test user', | ||
} | ||
|
||
describe('sendMessage', () => { | ||
it("should return a 401 if auth isn't provided", async () => { | ||
expect(handleSendMessage(defaultData, {} as any)).rejects.toThrow( | ||
new functions.https.HttpsError('unauthenticated', 'Unauthenticated'), | ||
) | ||
}) | ||
|
||
it('should return a 403 if the user is blocked', async () => { | ||
const context = { auth: { uid: 'abc' } } | ||
|
||
jest.mock('./messages', () => ({ | ||
isBlocked: () => jest.fn().mockResolvedValue(true), | ||
reachedLimit: () => jest.fn().mockResolvedValue(false), | ||
})) | ||
|
||
expect(handleSendMessage(defaultData, context as any)).rejects.toThrow( | ||
new functions.https.HttpsError('permission-denied', 'User is Blocked'), | ||
) | ||
}) | ||
|
||
it('should return a 429 if the user message limit exceeded', async () => { | ||
const context = { auth: { uid: 'abc' } } | ||
|
||
jest.mock('./messages', () => ({ | ||
isBlocked: () => jest.fn().mockResolvedValue(false), | ||
reachedLimit: () => jest.fn().mockResolvedValue(true), | ||
})) | ||
|
||
expect(handleSendMessage(defaultData, context as any)).rejects.toThrow( | ||
new functions.https.HttpsError('resource-exhausted', 'Limit exceeded'), | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import * as functions from 'firebase-functions' | ||
import { DB_ENDPOINTS } from '../models' | ||
import { firebaseAdmin } from '../Firebase/admin' | ||
import { createDoc } from '../Utils/doc.utils' | ||
|
||
import type { SendMessage } from 'oa-shared' | ||
|
||
const EMAIL_ADDRESS_SEND_LIMIT = 100 | ||
|
||
const isBlocked = async (uid: string) => { | ||
const userReq = await firebaseAdmin | ||
.firestore() | ||
.collection(DB_ENDPOINTS.users) | ||
.where('_authID', '==', uid) | ||
.get() | ||
|
||
const user = userReq.docs[0].data() | ||
|
||
return !!user.isBlockedFromMessaging | ||
} | ||
|
||
const reachedLimit = async (email: string) => { | ||
const userReq = await firebaseAdmin | ||
.firestore() | ||
.collection(DB_ENDPOINTS.messages) | ||
.where('email', '==', email) | ||
.count() | ||
.get() | ||
|
||
const count = userReq.data().count | ||
|
||
return count >= EMAIL_ADDRESS_SEND_LIMIT | ||
} | ||
|
||
export const handleSendMessage = async ( | ||
data: SendMessage, | ||
context: functions.https.CallableContext, | ||
) => { | ||
if (!context.auth) { | ||
throw new functions.https.HttpsError('unauthenticated', 'Unauthenticated') | ||
} | ||
|
||
if (await isBlocked(context.auth.uid)) { | ||
throw new functions.https.HttpsError('permission-denied', 'User is Blocked') | ||
} | ||
|
||
if (await reachedLimit(context.auth.token.email)) { | ||
throw new functions.https.HttpsError('resource-exhausted', 'Limit exceeded') | ||
} | ||
|
||
const newMessage = createDoc({ | ||
isSent: false, | ||
toUserName: data.to, | ||
email: context.auth.token.email, | ||
message: data.message, | ||
text: data.message, | ||
}) | ||
|
||
await firebaseAdmin | ||
.firestore() | ||
.collection(DB_ENDPOINTS.messages) | ||
.doc() | ||
.set(newMessage) | ||
} | ||
|
||
export const sendMessage = functions | ||
.runWith({ | ||
memory: '512MB', | ||
}) | ||
.https.onCall(handleSendMessage) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export type SendMessage = { | ||
to: string | ||
message: string | ||
name: string | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
export { UserContactError } from './UserContactError' | ||
export { UserContactFieldEmail } from './UserContactFieldEmail' | ||
export { UserContactFieldMessage } from './UserContactFieldMessage' | ||
export { UserContactFieldName } from './UserContactFieldName' | ||
export { UserContactNotLoggedIn } from './UserContactNotLoggedIn' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { getFunctions, httpsCallable } from 'firebase/functions' | ||
|
||
import type { SendMessage } from 'oa-shared' | ||
|
||
const functions = getFunctions() | ||
|
||
const sendMessageFunction = httpsCallable(functions, 'sendMessage') | ||
|
||
const sendMessage = async (data: SendMessage) => { | ||
await sendMessageFunction(data) | ||
} | ||
|
||
export const messageService = { | ||
sendMessage, | ||
} |
Oops, something went wrong.