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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongodb-backup-service",
3
- "version": "1.0.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 targetDate = dateStr || DateUtils.getCurrentDateString(configManager.get().schedule.timezone);
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', minute: 'numeric', hour12: false,
103
- year: 'numeric', month: '2-digit', day: '2-digit',
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 get = (type) => tzParts.find(p => p.type === type);
108
- const tzHour = parseInt(get('hour').value, 10);
109
- const tzMinute = parseInt(get('minute').value, 10);
110
- const tzDay = parseInt(get('day').value, 10);
111
- const tzWeekday = get('weekday').value.toLowerCase(); // e.g. 'wednesday'
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
- // Has the scheduled time already passed today (in configured timezone)?
116
- const passedToday = (tzHour > schedHour) || (tzHour === schedHour && tzMinute >= schedMin);
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) return currentDateStr;
120
- // Scheduled time hasn't arrived yet today — yesterday's backup is the expected one
121
- const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
122
- return new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(yesterday);
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
- if (tzWeekday === scheduledWeekday && passedToday) {
129
- return currentDateStr;
152
+
153
+ if (dayMap[scheduledWeekday] === undefined) {
154
+ return null;
130
155
  }
131
- // Otherwise: find the most recent occurrence of that weekday
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
- if (targetDow === undefined) return null;
136
- let daysBack = (currentDow - targetDow + 7) % 7;
137
- if (daysBack === 0) daysBack = 7; // same weekday but time hasn't passed
138
- const prevDate = new Date(now.getTime() - daysBack * 24 * 60 * 60 * 1000);
139
- return new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(prevDate);
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 scheduledDayOfMonth = parseInt(day, 10);
144
- if (tzDay === scheduledDayOfMonth && passedToday) {
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
- // Otherwise: check the previous month's occurrence
148
- // Go back to the 1st of this month, then subtract one day to get last month, then format day
149
- const prevMonthDate = new Date(now);
150
- prevMonthDate.setDate(0); // last day of previous month
151
- const prevMonthParts = new Intl.DateTimeFormat('en-US', { timeZone: tz, year: 'numeric', month: '2-digit' }).formatToParts(prevMonthDate);
152
- const prevYear = prevMonthParts.find(p => p.type === 'year').value;
153
- const prevMonth = prevMonthParts.find(p => p.type === 'month').value;
154
- const dayPadded = String(scheduledDayOfMonth).padStart(2, '0');
155
- return `${prevYear}-${prevMonth}-${dayPadded}`;
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
  }