Skip to content

Commit 19b2125

Browse files
authored
Merge pull request #1716 from Mephistic/email-update
First Notifications Cleanup Pass
2 parents 30501ce + c47d07a commit 19b2125

15 files changed

+192
-338
lines changed

.github/workflows/repo-checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
- uses: actions/checkout@v2
5050
- uses: ./.github/actions/setup-repo
5151
- name: Cache Emulators
52-
uses: actions/cache@v2
52+
uses: actions/cache@v4
5353
with:
5454
path: /home/runner/.cache/firebase/emulators
5555
key: ${{ runner.os }}-firebase-emulators-${{ hashFiles('~/.cache/firebase/emulators/**') }}

extensions/firestore-send-email.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
DEFAULT_FROM=[email protected]
2-
LOCATION=us-east4
3-
MAIL_COLLECTION=notification_mails
2+
LOCATION=us-central1
3+
MAIL_COLLECTION=emails
44
SMTP_CONNECTION_URI=smtps://[email protected]:465
55
TTL_EXPIRE_TYPE=never
66
TTL_EXPIRE_VALUE=1

firebase.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
"rules": "storage.rules"
4242
},
4343
"extensions": {
44-
"firestore-send-email": "firebase/[email protected].34"
44+
"firestore-send-email": "firebase/[email protected].36"
4545
}
4646
}

firestore.indexes.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,20 @@
786786
{ "queryScope": "COLLECTION", "order": "DESCENDING" },
787787
{ "queryScope": "COLLECTION", "arrayConfig": "CONTAINS" }
788788
]
789+
},
790+
{
791+
"collectionGroup": "userNotificationFeed",
792+
"queryScope": "COLLECTION",
793+
"fields": [
794+
{
795+
"fieldPath": "notification.type",
796+
"order": "ASCENDING"
797+
},
798+
{
799+
"fieldPath": "notification.timestamp",
800+
"order": "ASCENDING"
801+
}
802+
]
789803
}
790804
]
791805
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as handlebars from "handlebars"
2+
import * as fs from "fs"
3+
import * as path from "path"
4+
import * as helpers from "./helpers"
5+
6+
const PARTIALS_DIR = "../../lib/email/partials"
7+
8+
// Register Handlebars helper functions
9+
export const registerHelpers = () => {
10+
handlebars.registerHelper("toLowerCase", helpers.toLowerCase)
11+
handlebars.registerHelper("noUpdatesFormat", helpers.noUpdatesFormat)
12+
handlebars.registerHelper("isDefined", helpers.isDefined)
13+
}
14+
15+
// Register all Handlebars partials
16+
export const registerPartials = (directoryPath: string) => {
17+
const filenames = fs.readdirSync(directoryPath)
18+
filenames.forEach(filename => {
19+
const partialPath = path.join(directoryPath, filename)
20+
const stats = fs.statSync(partialPath)
21+
if (stats.isDirectory()) {
22+
registerPartials(partialPath)
23+
} else if (stats.isFile() && path.extname(filename) === ".handlebars") {
24+
const partialName = path.basename(filename, ".handlebars")
25+
const partialContent = fs.readFileSync(partialPath, "utf8")
26+
handlebars.registerPartial(partialName, partialContent)
27+
}
28+
})
29+
}
30+
31+
export const prepareHandlebars = () => {
32+
registerHelpers()
33+
registerPartials(path.join(__dirname, PARTIALS_DIR))
34+
}

functions/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export {
3636
populateTestimonySubmissionNotificationEvents,
3737
cleanupNotifications,
3838
deliverNotifications,
39-
httpsPublishNotifications,
4039
httpsDeliverNotifications,
4140
httpsCleanupNotifications,
4241
updateUserNotificationFrequency

functions/src/notifications/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,18 @@ The following cloud functions are involved in the notification process:
4242
1. **deliverNotifications**:
4343

4444
- Sends notifications to users who have a `notificationFrequency` of 'daily' and whose `nextDigestAt` is less than or equal to the current time.
45-
- Populates the `notification_mails` collection with a notification document.
45+
- Populates the `emails` collection with a notification document.
4646

4747
2. **cleanUpNotifications**:
4848
- Removes notifications from the users' userNotificationFeed collection that are older than 60 days.
49-
- Removes notifications from the events collection that are older than 60 days.
50-
- Removes notifications from the notifications_mails collection that are older than 60 days.
49+
- Removes notifications from the notificationEvents collection that are older than 60 days.
50+
- Removes notifications from the emails collection that are older than 60 days.
5151

5252
### Database Collections
5353

5454
- `activeTopicSubscriptions`: Stores the active topic subscriptions for users.
55-
- `notification_mails`: Stores the notification mails sent to users.
56-
- `events`: Stores the events that trigger notifications.
55+
- `emails`: Stores the notification mails sent to users.
56+
- `notificationEvents`: Stores the events that trigger notifications.
5757

5858
### Query Logic
5959

@@ -69,7 +69,7 @@ yarn firebase-admin -e local run-script <name-of-script>
6969

7070
## Future Considerations
7171

72-
- `publishNotifications` currently listens to the `events` collection but could be extended to include other collections.
72+
- `publishNotifications` currently listens to the `notificationEvents` collection but could be extended to include other collections.
7373
- Formatting of fields may need to be changed if scraping of hearings is included.
7474

7575
## References

functions/src/notifications/cleanupNotifications.ts

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,87 @@ import * as functions from "firebase-functions"
22
import * as admin from "firebase-admin"
33
import { Timestamp } from "../firebase"
44

5+
const RETENTION_PERIOD_DAYS = 60
6+
57
// Get a reference to the Firestore database
68
const db = admin.firestore()
79

10+
// TODO
11+
// 1.) Do we actually want to delete old notifications? (check with Matt V)
12+
// 2.) Do we actually want to delete old notificaiton events? Why not keep the history?
13+
// 3.) Should we just use the builtin TTL feature for all of these?
14+
// 4.) Should we use a bulk delete operation for firestore for all of these?
15+
16+
// Delete old notifications from userNotificationFeed collections
17+
const cleanupUserNotificationFeed = async (threshold: Timestamp) => {
18+
const notificationsSnapshot = await db
19+
.collectionGroup("userNotificationFeed")
20+
.where("createdAt", "<", threshold)
21+
.get()
22+
23+
const deleteNotificationPromises = notificationsSnapshot.docs.map(doc =>
24+
doc.ref.delete()
25+
)
26+
await Promise.all(deleteNotificationPromises)
27+
}
28+
29+
const cleanupNotificationEvents = async (threshold: Timestamp) => {
30+
// Delete old topic events from the events collection
31+
const notificationEventsSnapshot = await db
32+
.collection("notificationEvents")
33+
.where("createdAt", "<", threshold)
34+
.get()
35+
36+
const deleteNotificationEventPromises = notificationEventsSnapshot.docs.map(
37+
doc => doc.ref.delete()
38+
)
39+
await Promise.all(deleteNotificationEventPromises)
40+
}
41+
42+
const cleanupNotificationEmails = async (threshold: Timestamp) => {
43+
// Delete old email documents from the emails collection
44+
const emailsSnapshot = await db
45+
.collection("emails")
46+
.where("createdAt", "<", threshold)
47+
.get()
48+
49+
const deleteEmailPromises = emailsSnapshot.docs.map(doc => doc.ref.delete())
50+
await Promise.all(deleteEmailPromises)
51+
}
52+
53+
const runCleanupNotifications = async () => {
54+
// todo better date math
55+
// Define the time threshold for old notifications, topic events, and email documents
56+
const threshold = new Timestamp(
57+
Date.now() - RETENTION_PERIOD_DAYS * 24 * 60 * 60 * 1000,
58+
0
59+
)
60+
61+
await cleanupUserNotificationFeed(threshold)
62+
await cleanupNotificationEvents(threshold)
63+
await cleanupNotificationEmails(threshold)
64+
}
65+
866
// Define the cleanupNotifications function
967
export const cleanupNotifications = functions.pubsub
1068
.schedule("every 24 hours")
11-
.onRun(async context => {
12-
// Define the time threshold for old notifications, topic events, and email documents
13-
const retentionPeriodDays = 60 // Adjust this value as needed
14-
const threshold = new Timestamp(
15-
Date.now() - retentionPeriodDays * 24 * 60 * 60 * 1000,
16-
0
17-
)
18-
19-
// Delete old notifications from userNotificationFeed collections
20-
const notificationsSnapshot = await db
21-
.collectionGroup("userNotificationFeed")
22-
.where("createdAt", "<", threshold)
23-
.get()
24-
25-
const deleteNotificationPromises = notificationsSnapshot.docs.map(doc =>
26-
doc.ref.delete()
27-
)
28-
await Promise.all(deleteNotificationPromises)
29-
30-
// Delete old topic events from the events collection
31-
const eventsSnapshot = await db
32-
.collection("events")
33-
.where("createdAt", "<", threshold)
34-
.get()
35-
36-
const deleteTopicEventPromises = eventsSnapshot.docs.map(doc =>
37-
doc.ref.delete()
38-
)
39-
await Promise.all(deleteTopicEventPromises)
40-
41-
// Delete old email documents from the notifications_mails collection
42-
const emailsSnapshot = await db
43-
.collection("notifications_mails")
44-
.where("createdAt", "<", threshold)
45-
.get()
46-
47-
const deleteEmailPromises = emailsSnapshot.docs.map(doc => doc.ref.delete())
48-
await Promise.all(deleteEmailPromises)
49-
})
69+
.onRun(runCleanupNotifications)
70+
71+
export const httpsCleanupNotifications = functions.https.onRequest(
72+
async (request, response) => {
73+
try {
74+
console.log("httpsCleanupNotifications triggered")
75+
await runCleanupNotifications()
76+
console.log("DEBUG: cleanupNotifications completed")
77+
78+
response
79+
.status(200)
80+
.send(
81+
"Successfully cleaned up old notifications, topic events, and email documents"
82+
)
83+
} catch (error) {
84+
console.error("Error in httpsCleanupNotifications:", error)
85+
response.status(500).send("Internal server error")
86+
}
87+
}
88+
)

0 commit comments

Comments
 (0)