Skip to content

Commit 611389d

Browse files
committed
add mail with resend on success and/or failure
1 parent ea1e1d3 commit 611389d

8 files changed

+891
-16
lines changed

.env.example

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ OVH_OS_IMAGES_BACKUP_CONTAINER_ENDPOINT=
88

99
# OVH MongoDB
1010
DATABASE_NAME=
11-
DATABASE_URI=replicaSet=
11+
DATABASE_URI=
12+
13+
# Resend
14+
RESEND_KEY=
15+
MAIL_TO=

config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ export default {
22
blobBackupBucketPrefix: 'blob-backup',
33
dbBackupBucketPrefix: 'db-backup',
44
keepAmountOfBackups: 4,
5+
sendMailOnFailure: true,
6+
sendMailOnSuccess: true,
57
}

cron-jobs/backups-cleanup.ts

+24-4
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import { S3Helper } from '../helpers/s3.ts';
77
import config from '../config.ts';
88
import { sortBucketsNewestFirst } from '../helpers/date.ts';
9+
import mail from '../helpers/mail.ts';
910

10-
const cleanUpBucketsWithPrefix = async (prefix, allBuckets, s3Helper) => {
11+
const cleanUpBucketsWithPrefix = async (prefix, allBuckets, s3Helper): Promise<[String?]> => {
1112
/*
1213
// If run as cron-job on vercel, make sure that only cron-jobs can execute
1314
// the script.
@@ -26,20 +27,39 @@ const cleanUpBucketsWithPrefix = async (prefix, allBuckets, s3Helper) => {
2627
for (const bucketToDelete of bucketsToDelete) {
2728
await s3Helper.deleteBucket(bucketToDelete.Name);
2829
}
30+
31+
if (bucketsToDelete.length === 0) {
32+
return [];
33+
}
34+
35+
return [bucketsToDelete.map((bucket) => bucket.Name)];
2936
}
3037

3138
const main = async () => {
3239
try {
3340
const s3Helper = new S3Helper();
3441
const buckets = await s3Helper.getAllBuckets();
3542

36-
await cleanUpBucketsWithPrefix(config.blobBackupBucketPrefix, buckets, s3Helper);
37-
await cleanUpBucketsWithPrefix(config.dbBackupBucketPrefix, buckets, s3Helper);
43+
const deletedBlobBuckets = await cleanUpBucketsWithPrefix(config.blobBackupBucketPrefix, buckets, s3Helper);
44+
const deletedDbBuckets = await cleanUpBucketsWithPrefix(config.dbBackupBucketPrefix, buckets, s3Helper);
45+
46+
const mailMessage = `Deleted ${deletedBlobBuckets.length} blob buckets and ${deletedDbBuckets.length} db buckets.<br><br>Deleted blob buckets: ${deletedBlobBuckets.join('<br>')}<br><br>Deleted db buckets: ${deletedDbBuckets.join('<br>')}`
47+
await mail(
48+
'--> Backups cleanup success',
49+
mailMessage,
50+
false,
51+
);
3852

3953
console.log('--> Backups cleanup done');
40-
54+
4155

4256
} catch (error) {
57+
await mail(
58+
'--> Backups cleanup failure',
59+
error,
60+
true,
61+
);
62+
4363
console.log(error);
4464
}
4565
}

cron-jobs/blob-backup.ts

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
// Ideally executed as cron-job.
22

3-
// todo:
4-
// - integrity check: after backup, count objects in blob and in backup
5-
// - log amount of objects
6-
73
import { Readable } from 'node:stream';
84
import { ReadableStream } from 'node:stream/web';
95
import * as blobHelpers from '../helpers/blob.ts';
106
import { S3Helper } from '../helpers/s3.ts';
117
import { dateString } from '../helpers/date.ts';
128
import config from '../config.ts';
9+
import mail from '../helpers/mail.ts';
1310

1411
const main = async () => {
1512
/*
@@ -45,9 +42,30 @@ const main = async () => {
4542
})
4643
);
4744

48-
console.log('-->> Backup done: Vercel Blob data to OVH S3');
45+
// integrity check
46+
const bucketItemsCount = await s3Helper.listObjectsOfBucket(bucketName);
47+
48+
if (bucketItemsCount.length !== blobs.length) {
49+
throw new Error(`Blob Backup failure during integrity check. Vercel blob has ${blobs.length} objects, but the backup contains ${bucketItemsCount.length}`);
50+
}
51+
52+
const mailMessage = `Successfully backed up ${blobs.length} items from Vercel Blob to OVH S3`;
53+
54+
await mail(
55+
'--> Backup done: Vercel Blob data to OVH S3',
56+
mailMessage,
57+
false,
58+
);
59+
60+
console.log(mailMessage);
4961

5062
} catch (error) {
63+
await mail(
64+
'--> Backup failure: Vercel Blob data to OVH S3',
65+
error,
66+
true,
67+
);
68+
5169
console.log(error);
5270
}
5371
}

cron-jobs/db-backup.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { S3Helper } from '../helpers/s3.ts';
55
import { DbHelper } from '../helpers/db.ts';
66
import { dateString } from '../helpers/date.ts';
77
import config from '../config.ts';
8+
import mail from '../helpers/mail.ts';
89

910
dotenv.config();
1011

@@ -30,26 +31,40 @@ const main = async () => {
3031
await s3Helper.createBucket(bucketName);
3132

3233
if (!process.env.DATABASE_NAME) {
33-
console.log('Aborting. DATABASE_NAME is not defined in env.');
34-
35-
return;
34+
throw new Error('Aborting. DATABASE_NAME is not defined in env.');
3635
}
3736

3837
const collections = await dbHelper.getCollections(process.env.DATABASE_NAME);
38+
let collectionBackupCount = 0;
3939

4040
for (const collection of collections) {
4141
const { collectionName } = collection;
4242

4343
if (!collectionName.startsWith('system.')) {
44+
collectionBackupCount++;
4445
const results = await collection.find({}).toArray();
4546

4647
await s3Helper.addObject(bucketName, `${collectionName}.json`, JSON.stringify(results));
4748
}
4849
}
4950

50-
console.log('-->> Backup done: DB on OVH to OVH S3');
51+
const mailMessage = `Successfully backed up ${collectionBackupCount} colletions from MongoDb to OVH S3`;
52+
53+
await mail(
54+
'-->> Backup done: DB on OVH to OVH S3',
55+
mailMessage,
56+
false,
57+
);
58+
59+
console.log(mailMessage);
5160

5261
} catch (error) {
62+
await mail(
63+
'--> Backup failure: DB on OVH to OVH S3',
64+
error,
65+
true,
66+
);
67+
5368
console.log(error);
5469
} finally {
5570
dbHelper.getClient().close();

helpers/mail.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Resend } from 'resend';
2+
import dotenv from 'dotenv';
3+
import config from '../config.ts';
4+
5+
dotenv.config();
6+
7+
export default async (subject: string, message: string, failure: boolean): Promise<void> => {
8+
9+
if (!process.env.RESEND_KEY || !process.env.MAIL_TO) {
10+
return;
11+
}
12+
13+
if (failure) {
14+
if (!config.sendMailOnFailure) {
15+
return;
16+
}
17+
} else {
18+
if (!config.sendMailOnSuccess) {
19+
return;
20+
}
21+
}
22+
23+
const messageContent = typeof message === 'object'
24+
? JSON.stringify(message)
25+
: message;
26+
27+
try {
28+
const resend = new Resend(process.env.RESEND_KEY);
29+
30+
await resend.emails.send({
31+
32+
to: process.env.MAIL_TO,
33+
subject,
34+
html: messageContent,
35+
});
36+
} catch (err) {
37+
console.log('Error sending mail');
38+
39+
console.log(err);
40+
41+
}
42+
}

0 commit comments

Comments
 (0)