mongodb-backup-service 1.0.0 → 1.0.2
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/package.json +2 -2
- package/src/index.js +7 -3
- package/src/scheduler/scheduler.js +136 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongodb-backup-service",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Reusable Node.js service for automated MongoDB backups with scheduling, startup recovery, Excel export, retention, email notifications, and backup locking.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
"node": ">=18.0.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
+
"dotenv": "^17.4.2",
|
|
49
50
|
"exceljs": "^4.4.0",
|
|
50
51
|
"mongodb": "^7.6.0",
|
|
51
52
|
"node-cron": "^4.0.0",
|
|
52
53
|
"nodemailer": "^10.0.0"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"dotenv": "^17.4.2",
|
|
56
56
|
"jest": "^30.5.1"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/index.js
CHANGED
|
@@ -46,10 +46,14 @@ class DatabaseBackupService {
|
|
|
46
46
|
if (!this.backupManager) {
|
|
47
47
|
this.backupManager = new BackupManager();
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
const DateUtils = require('./utils/dateUtils');
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
const config = configManager.get();
|
|
52
|
+
|
|
53
|
+
const targetDate = dateStr || DateUtils.getCurrentDateString(
|
|
54
|
+
config.schedule?.timezone || 'UTC'
|
|
55
|
+
);
|
|
56
|
+
|
|
53
57
|
return this.backupManager.storage.readMetadata(targetDate);
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -91,70 +91,162 @@ class Scheduler {
|
|
|
91
91
|
getMostRecentExpectedBackupDate() {
|
|
92
92
|
const tz = this.config.schedule.timezone;
|
|
93
93
|
const { type, time, day } = this.config.schedule;
|
|
94
|
+
|
|
94
95
|
const [hourStr, minuteStr] = time.split(':');
|
|
95
96
|
const schedHour = parseInt(hourStr, 10);
|
|
96
97
|
const schedMin = parseInt(minuteStr, 10);
|
|
97
|
-
|
|
98
|
-
// Get the current time parts in the configured timezone (no moment.js needed)
|
|
98
|
+
|
|
99
99
|
const now = new Date();
|
|
100
|
+
|
|
100
101
|
const tzParts = new Intl.DateTimeFormat('en-US', {
|
|
101
102
|
timeZone: tz,
|
|
102
|
-
hour: 'numeric',
|
|
103
|
-
|
|
103
|
+
hour: 'numeric',
|
|
104
|
+
minute: 'numeric',
|
|
105
|
+
hour12: false,
|
|
106
|
+
year: 'numeric',
|
|
107
|
+
month: '2-digit',
|
|
108
|
+
day: '2-digit',
|
|
104
109
|
weekday: 'long'
|
|
105
110
|
}).formatToParts(now);
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
111
|
+
|
|
112
|
+
const getPart = (type) =>
|
|
113
|
+
tzParts.find((part) => part.type === type)?.value;
|
|
114
|
+
|
|
115
|
+
const tzHour = parseInt(getPart('hour'), 10);
|
|
116
|
+
const tzMinute = parseInt(getPart('minute'), 10);
|
|
117
|
+
const tzDay = parseInt(getPart('day'), 10);
|
|
118
|
+
const tzWeekday = getPart('weekday').toLowerCase();
|
|
119
|
+
|
|
113
120
|
const currentDateStr = DateUtils.getCurrentDateString(tz);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
|
|
122
|
+
const passedToday =
|
|
123
|
+
tzHour > schedHour ||
|
|
124
|
+
(tzHour === schedHour && tzMinute >= schedMin);
|
|
125
|
+
|
|
126
|
+
const dayMap = {
|
|
127
|
+
sunday: 0,
|
|
128
|
+
monday: 1,
|
|
129
|
+
tuesday: 2,
|
|
130
|
+
wednesday: 3,
|
|
131
|
+
thursday: 4,
|
|
132
|
+
friday: 5,
|
|
133
|
+
saturday: 6
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------
|
|
137
|
+
// DAILY
|
|
138
|
+
// ---------------------------------------------------------
|
|
118
139
|
if (type.toLowerCase() === 'daily') {
|
|
119
|
-
if (passedToday)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
140
|
+
if (!passedToday) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return currentDateStr;
|
|
123
145
|
}
|
|
124
|
-
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------
|
|
148
|
+
// WEEKLY
|
|
149
|
+
// ---------------------------------------------------------
|
|
125
150
|
if (type.toLowerCase() === 'weekly') {
|
|
126
|
-
// Check if today is the scheduled weekday AND the time has passed
|
|
127
151
|
const scheduledWeekday = (day || '').toLowerCase();
|
|
128
|
-
|
|
129
|
-
|
|
152
|
+
|
|
153
|
+
if (dayMap[scheduledWeekday] === undefined) {
|
|
154
|
+
return null;
|
|
130
155
|
}
|
|
131
|
-
|
|
132
|
-
const dayMap = { 'sunday': 0, 'monday': 1, 'tuesday': 2, 'wednesday': 3, 'thursday': 4, 'friday': 5, 'saturday': 6 };
|
|
133
|
-
const targetDow = dayMap[scheduledWeekday];
|
|
156
|
+
|
|
134
157
|
const currentDow = dayMap[tzWeekday];
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
const targetDow = dayMap[scheduledWeekday];
|
|
159
|
+
|
|
160
|
+
// Today is the scheduled weekday
|
|
161
|
+
if (currentDow === targetDow) {
|
|
162
|
+
// Scheduled time has not arrived yet.
|
|
163
|
+
// Therefore there is no missed backup yet.
|
|
164
|
+
if (!passedToday) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Scheduled time has passed today.
|
|
169
|
+
return currentDateStr;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Today is after the scheduled weekday.
|
|
173
|
+
// Find the most recent scheduled weekday.
|
|
174
|
+
const daysBack = (currentDow - targetDow + 7) % 7;
|
|
175
|
+
|
|
176
|
+
const previousOccurrence = new Date(
|
|
177
|
+
now.getTime() - daysBack * 24 * 60 * 60 * 1000
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return new Intl.DateTimeFormat('en-CA', {
|
|
181
|
+
timeZone: tz,
|
|
182
|
+
year: 'numeric',
|
|
183
|
+
month: '2-digit',
|
|
184
|
+
day: '2-digit'
|
|
185
|
+
}).format(previousOccurrence);
|
|
140
186
|
}
|
|
141
|
-
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------
|
|
189
|
+
// MONTHLY
|
|
190
|
+
// ---------------------------------------------------------
|
|
142
191
|
if (type.toLowerCase() === 'monthly') {
|
|
143
|
-
const
|
|
144
|
-
|
|
192
|
+
const scheduledDay = parseInt(day, 10);
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
Number.isNaN(scheduledDay) ||
|
|
196
|
+
scheduledDay < 1 ||
|
|
197
|
+
scheduledDay > 28
|
|
198
|
+
) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Today is the scheduled day.
|
|
203
|
+
if (tzDay === scheduledDay) {
|
|
204
|
+
// Scheduled time has not arrived yet.
|
|
205
|
+
if (!passedToday) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
145
209
|
return currentDateStr;
|
|
146
210
|
}
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
211
|
+
|
|
212
|
+
// Today is after the scheduled day.
|
|
213
|
+
if (tzDay > scheduledDay) {
|
|
214
|
+
const currentYear = parseInt(
|
|
215
|
+
getPart('year'),
|
|
216
|
+
10
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const currentMonth = parseInt(
|
|
220
|
+
getPart('month'),
|
|
221
|
+
10
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return `${currentYear}-${String(currentMonth).padStart(2, '0')}-${String(scheduledDay).padStart(2, '0')}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Today is before the scheduled day.
|
|
228
|
+
// Therefore the most recent occurrence was last month.
|
|
229
|
+
const currentYear = parseInt(
|
|
230
|
+
getPart('year'),
|
|
231
|
+
10
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const currentMonth = parseInt(
|
|
235
|
+
getPart('month'),
|
|
236
|
+
10
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
let previousYear = currentYear;
|
|
240
|
+
let previousMonth = currentMonth - 1;
|
|
241
|
+
|
|
242
|
+
if (previousMonth === 0) {
|
|
243
|
+
previousMonth = 12;
|
|
244
|
+
previousYear--;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return `${previousYear}-${String(previousMonth).padStart(2, '0')}-${String(scheduledDay).padStart(2, '0')}`;
|
|
156
248
|
}
|
|
157
|
-
|
|
249
|
+
|
|
158
250
|
return null;
|
|
159
251
|
}
|
|
160
252
|
}
|