dbdock 1.1.0 → 1.1.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/README.md CHANGED
@@ -24,7 +24,7 @@ npx dbdock restore # Restore backup
24
24
  - **Security** - AES-256 encryption, Brotli compression
25
25
  - **Retention Policies** - Automatic cleanup by count/age with safety nets
26
26
  - **Smart UX** - Intelligent filtering for 100+ backups, clear error messages
27
- - **Email Alerts** - SMTP notifications with custom templates
27
+ - **Alerts** - Email (SMTP) and Slack notifications for backups (CLI & Programmatic)
28
28
  - **TypeScript Native** - Full type safety for programmatic usage
29
29
  - **Automation** - Cron schedules, auto-cleanup after backups
30
30
 
@@ -33,7 +33,7 @@ npx dbdock restore # Restore backup
33
33
  **Global Installation (Recommended):**
34
34
 
35
35
  ```bash
36
- npm install -g dbdock-cli
36
+ npm install -g dbdock
37
37
 
38
38
  dbdock init # Use directly
39
39
  dbdock backup
@@ -57,7 +57,7 @@ Interactive setup wizard that creates `dbdock.config.json` with:
57
57
  - Database connection (host, port, credentials)
58
58
  - Storage provider (Local, S3, R2, Cloudinary)
59
59
  - Encryption/compression settings
60
- - Email alerts (optional)
60
+ - Email and Slack alerts (optional)
61
61
 
62
62
  Auto-adds config to `.gitignore` to protect credentials.
63
63
 
@@ -115,6 +115,15 @@ Progress:
115
115
  - Date range (24h, 7d, 30d, 90d, custom)
116
116
  - Search by keyword/ID
117
117
 
118
+ **Migration Support:**
119
+
120
+ You can choose to restore to a **New Database Instance** during the restore process. This is perfect for migrating data between servers (e.g., from staging to production or local to cloud).
121
+
122
+ 1. Run `npx dbdock restore`
123
+ 2. Select a backup
124
+ 3. Choose "New Database Instance (Migrate)"
125
+ 4. Enter connection details for the target database
126
+
118
127
  Shows database stats and requires confirmation before restore.
119
128
 
120
129
  ### `npx dbdock list`
@@ -210,7 +219,7 @@ dbdock schedule
210
219
  - Every month (1st): `0 0 1 * *`
211
220
  - Custom cron expression
212
221
 
213
- **⚠️ Important:** Schedules only execute when DBDock is integrated into your NestJS application (see Programmatic Usage below). The CLI is for configuration only.
222
+ **⚠️ Important:** Schedules only execute when DBDock is integrated into your Node.js application (see Programmatic Usage below). The CLI is for configuration only.
214
223
 
215
224
  ## Configuration
216
225
 
@@ -367,127 +376,330 @@ Automatic cleanup to prevent storage bloat from frequent backups:
367
376
 
368
377
  ## Programmatic Usage
369
378
 
370
- ### NestJS Integration
379
+ Use DBDock in your Node.js application to create backups programmatically. You don't need to understand NestJS internals - DBDock provides a simple API that works with any Node.js backend.
371
380
 
372
- ```typescript
373
- import { Module } from '@nestjs/common';
374
- import { DBDockModule } from 'dbdock';
375
-
376
- @Module({
377
- imports: [
378
- DBDockModule.forRoot({
379
- database: {
380
- type: 'postgres',
381
- host: 'localhost',
382
- port: 5432,
383
- username: 'postgres',
384
- password: process.env.DB_PASSWORD,
385
- database: 'myapp',
386
- },
387
- storage: {
388
- provider: 's3',
389
- s3: {
390
- bucket: 'my-backups',
391
- region: 'us-east-1',
392
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
393
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
394
- },
395
- },
396
- backup: {
397
- compression: { enabled: true, level: 6 },
398
- encryption: { enabled: true, key: process.env.ENCRYPTION_KEY },
399
- schedules: [
400
- { name: 'Daily Backup', cron: '0 2 * * *', enabled: true },
401
- { name: 'Weekly Full', cron: '0 0 * * 0', enabled: true },
402
- ],
403
- },
404
- }),
405
- ],
406
- })
407
- export class AppModule {}
381
+ ### Basic Setup
382
+
383
+ First, install DBDock:
384
+
385
+ ```bash
386
+ npm install dbdock
408
387
  ```
409
388
 
410
- ### Creating and Restoring Backups
389
+ Make sure you have `dbdock.config.json` configured (run `npx dbdock init` first). DBDock reads all configuration from this file automatically.
411
390
 
412
- ```typescript
413
- import { BackupService } from 'dbdock';
391
+ ### How It Works
414
392
 
415
- @Injectable()
416
- export class MyService {
417
- constructor(private backupService: BackupService) {}
393
+ DBDock uses a simple initialization pattern:
418
394
 
419
- async createBackup() {
420
- const result = await this.backupService.createBackup({
421
- compress: true,
422
- encrypt: true,
423
- });
395
+ 1. Call `createDBDock()` to initialize DBDock (reads from `dbdock.config.json`)
396
+ 2. Get the `BackupService` from the returned context using `.get(BackupService)`
397
+ 3. Use the service methods to create backups, list backups, etc.
424
398
 
425
- console.log(`Backup created: ${result.metadata.id}`);
426
- return result;
427
- }
399
+ Think of `createDBDock()` as a factory function that sets up everything for you based on your config file.
428
400
 
429
- async restore(backupId: string) {
430
- await this.backupService.restoreBackup(backupId);
431
- }
401
+ ### Creating Backups
402
+
403
+ ```javascript
404
+ const { createDBDock, BackupService } = require('dbdock');
405
+
406
+ async function createBackup() {
407
+ const dbdock = await createDBDock();
408
+ const backupService = dbdock.get(BackupService);
409
+
410
+ const result = await backupService.createBackup({
411
+ format: 'plain', // 'custom' (binary), 'plain' (sql), 'directory', 'tar'
412
+ compress: true,
413
+ encrypt: true,
414
+ });
415
+
416
+ console.log(`Backup created: ${result.metadata.id}`);
417
+ console.log(`Size: ${result.metadata.formattedSize}`); // e.g. "108.3 KB"
418
+ console.log(`Path: ${result.storageKey}`);
419
+
420
+ return result;
432
421
  }
422
+
423
+ createBackup().catch(console.error);
433
424
  ```
434
425
 
435
- ### Schedule Management API
426
+ **Backup Options:**
436
427
 
437
- ```typescript
438
- import { ScheduleManager } from 'dbdock';
428
+ - `compress` - Enable/disable compression (default: from config)
429
+ - `encrypt` - Enable/disable encryption (default: from config)
430
+ - `format` - Backup format: `'custom'` (default), `'plain'`, `'directory'`, `'tar'`
431
+ - `type` - Backup type: `'full'` (default), `'schema'`, `'data'`
439
432
 
440
- const scheduleManager = new ScheduleManager();
433
+ ### Listing Backups
441
434
 
442
- scheduleManager.addSchedule({
443
- name: 'Hourly Backup',
444
- cron: '0 * * * *',
445
- enabled: true,
446
- });
435
+ ```javascript
436
+ const { createDBDock, BackupService } = require('dbdock');
447
437
 
448
- const schedules = scheduleManager.getSchedules();
449
- console.log('All schedules:', schedules);
438
+ async function listBackups() {
439
+ const dbdock = await createDBDock();
440
+ const backupService = dbdock.get(BackupService);
450
441
 
451
- scheduleManager.updateSchedule('Hourly Backup', {
452
- cron: '0 */2 * * *',
453
- });
442
+ const backups = await backupService.listBackups();
443
+
444
+ console.log(`Found ${backups.length} backups:`);
445
+ backups.forEach(
446
+ (backup: {
447
+ id: string;
448
+ formattedSize: string;
449
+ startTime: string | Date;
450
+ }) => {
451
+ console.log(
452
+ `- ${backup.id} (${backup.formattedSize}, created: ${backup.startTime})`
453
+ );
454
+ }
455
+ );
454
456
 
455
- scheduleManager.enableSchedule('Daily Backup');
456
- scheduleManager.disableSchedule('Weekly Full');
457
+ return backups;
458
+ }
457
459
 
458
- scheduleManager.removeSchedule('Old Schedule');
460
+ listBackups().catch(console.error);
459
461
  ```
460
462
 
461
- ### Complete Example with Schedules
463
+ ### Getting Backup Metadata
462
464
 
463
- ```typescript
464
- import { NestFactory } from '@nestjs/core';
465
- import { AppModule } from './app.module';
466
- import { ScheduleManager } from 'dbdock';
465
+ ```javascript
466
+ const { createDBDock, BackupService } = require('dbdock');
467
467
 
468
- async function bootstrap() {
469
- const scheduleManager = new ScheduleManager();
468
+ async function getBackupInfo(backupId) {
469
+ const dbdock = await createDBDock();
470
+ const backupService = dbdock.get(BackupService);
470
471
 
471
- scheduleManager.addSchedule({
472
- name: 'Daily Backup',
473
- cron: '0 2 * * *',
474
- enabled: true,
472
+ const metadata = await backupService.getBackupMetadata(backupId);
473
+
474
+ if (!metadata) {
475
+ console.log('Backup not found');
476
+ return null;
477
+ }
478
+
479
+ console.log('Backup details:', {
480
+ id: metadata.id,
481
+ size: metadata.size,
482
+ created: metadata.startTime,
483
+ encrypted: !!metadata.encryption,
484
+ compressed: metadata.compression.enabled,
475
485
  });
486
+
487
+ return metadata;
488
+ }
476
489
 
477
- scheduleManager.addSchedule({
478
- name: 'Weekly Archive',
479
- cron: '0 0 * * 0',
480
- enabled: true,
481
- });
490
+ getBackupInfo('your-backup-id').catch(console.error);
491
+ ```
492
+
493
+ **Note:** Restore functionality is currently only available via CLI (`npx dbdock restore`). Programmatic restore will be available in a future release.
494
+
495
+ ### Scheduling Backups
496
+
497
+ DBDock doesn't include a built-in scheduler (to keep the package lightweight), but it's easy to schedule backups using `node-cron`.
498
+
499
+ First, install `node-cron`:
500
+
501
+ ```bash
502
+ npm install node-cron
503
+ npm install --save-dev @types/node-cron
504
+ ```
482
505
 
483
- const app = await NestFactory.create(AppModule);
484
- await app.listen(3000);
506
+ Then create a scheduler script (e.g., `scheduler.ts`):
485
507
 
486
- console.log('Application started with scheduled backups');
508
+ ```typescript
509
+ import { createDBDock, BackupService } from 'dbdock';
510
+ import * as cron from 'node-cron';
511
+
512
+ async function startScheduler() {
513
+ // Initialize DBDock
514
+ const dbdock = await createDBDock();
515
+ const backupService = dbdock.get(BackupService);
516
+
517
+ console.log('🚀 Backup scheduler started. Running every minute...');
518
+
519
+ // Schedule task to run every minute ('* * * * *')
520
+ // For every 5 minutes use: '*/5 * * * *'
521
+ // For every hour use: '0 * * * *'
522
+ cron.schedule('* * * * *', async () => {
523
+ try {
524
+ console.log('\n⏳ Starting scheduled backup...');
525
+
526
+ const result = await backupService.createBackup({
527
+ format: 'plain', // Use 'plain' for SQL text, 'custom' for binary
528
+ compress: true,
529
+ encrypt: true,
530
+ });
531
+
532
+ console.log(`✅ Backup successful: ${result.metadata.id}`);
533
+ console.log(`📦 Size: ${result.metadata.formattedSize}`);
534
+ console.log(`📂 Path: ${result.storageKey}`);
535
+ } catch (error) {
536
+ console.error('❌ Backup failed:', error);
537
+ }
538
+ });
487
539
  }
488
540
 
489
- bootstrap();
541
+ startScheduler().catch(console.error);
542
+ ```
543
+
544
+ **Note:** The CLI `dbdock schedule` command manages configuration for external schedulers but does not run a daemon itself. Using `node-cron` as shown above is the recommended way to run scheduled backups programmatically.
545
+
546
+ ### Alerts
547
+
548
+ DBDock can send notifications when backups complete (success or failure) via Email and Slack. Alerts work with both **programmatic usage** and **CLI commands**.
549
+
550
+ **Configuration in `dbdock.config.json`:**
551
+
552
+ ```json
553
+ {
554
+ "database": { ... },
555
+ "storage": { ... },
556
+ "backup": { ... },
557
+ "alerts": {
558
+ "email": {
559
+ "enabled": true,
560
+ "smtp": {
561
+ "host": "smtp.gmail.com",
562
+ "port": 587,
563
+ "secure": false,
564
+ "auth": {
565
+ "user": "your-email@gmail.com",
566
+ "pass": "your-app-password"
567
+ }
568
+ },
569
+ "from": "backups@yourapp.com",
570
+ "to": ["admin@yourapp.com", "devops@yourapp.com"]
571
+ },
572
+ "slack": {
573
+ "enabled": true,
574
+ "webhookUrl": "https://hooks.slack.com/services/..."
575
+ }
576
+ }
577
+ }
578
+ ```
579
+
580
+ **Slack Configuration:**
581
+
582
+ 1. Create a Slack App or use an existing one.
583
+ 2. Enable "Incoming Webhooks".
584
+ 3. Create a new Webhook URL for your channel.
585
+ 4. Run `npx dbdock init` and paste the URL when prompted.
586
+
587
+ **SMTP Provider Examples:**
588
+
589
+ _Gmail:_
590
+ ```json
591
+ {
592
+ "smtp": {
593
+ "host": "smtp.gmail.com",
594
+ "port": 587,
595
+ "secure": false,
596
+ "auth": {
597
+ "user": "your-email@gmail.com",
598
+ "pass": "your-app-password"
599
+ }
600
+ }
601
+ }
602
+ ```
603
+
604
+ > **Note:** For Gmail, you need to [create an App Password](https://support.google.com/accounts/answer/185833) instead of using your regular password.
605
+
606
+ _SendGrid:_
607
+ ```json
608
+ {
609
+ "smtp": {
610
+ "host": "smtp.sendgrid.net",
611
+ "port": 587,
612
+ "secure": false,
613
+ "auth": {
614
+ "user": "apikey",
615
+ "pass": "YOUR_SENDGRID_API_KEY"
616
+ }
617
+ }
618
+ }
619
+ ```
620
+
621
+ _AWS SES:_
622
+ ```json
623
+ {
624
+ "smtp": {
625
+ "host": "email-smtp.us-east-1.amazonaws.com",
626
+ "port": 587,
627
+ "secure": false,
628
+ "auth": {
629
+ "user": "YOUR_SMTP_USERNAME",
630
+ "pass": "YOUR_SMTP_PASSWORD"
631
+ }
632
+ }
633
+ }
634
+ ```
635
+
636
+ _Mailgun:_
637
+ ```json
638
+ {
639
+ "smtp": {
640
+ "host": "smtp.mailgun.org",
641
+ "port": 587,
642
+ "secure": false,
643
+ "auth": {
644
+ "user": "postmaster@your-domain.mailgun.org",
645
+ "pass": "YOUR_MAILGUN_SMTP_PASSWORD"
646
+ }
647
+ }
648
+ }
649
+ ```
650
+
651
+ **Using Alerts Programmatically:**
652
+
653
+ Once configured in `dbdock.config.json`, alerts are sent automatically when you create backups programmatically:
654
+
655
+ ```javascript
656
+ const { createDBDock, BackupService } = require('dbdock');
657
+
658
+ async function createBackupWithAlerts() {
659
+ const dbdock = await createDBDock();
660
+ const backupService = dbdock.get(BackupService);
661
+
662
+ // Alerts will be sent automatically after backup completes
663
+ const result = await backupService.createBackup({
664
+ compress: true,
665
+ encrypt: true,
666
+ });
667
+
668
+ console.log(`Backup created: ${result.metadata.id}`);
669
+ // Alerts sent to configured channels
670
+ }
671
+
672
+ createBackupWithAlerts().catch(console.error);
490
673
  ```
674
+
675
+ **Alert Content:**
676
+
677
+ Success alerts include:
678
+ - Backup ID
679
+ - Database name
680
+ - Size (original and compressed)
681
+ - Duration
682
+ - Storage location
683
+ - Encryption status
684
+
685
+ Failure alerts include:
686
+ - Error message
687
+ - Database details
688
+ - Timestamp
689
+ - Helpful troubleshooting tips
690
+
691
+ **Testing Alert Configuration:**
692
+
693
+ Run `npx dbdock test` to validate your configuration without creating a backup.
694
+
695
+ **Important Notes:**
696
+
697
+ - ✅ Alerts work with programmatic usage (`createBackup()`)
698
+ - ✅ Alerts work with scheduled backups (cron jobs in your app)
699
+ - ✅ Alerts work with CLI commands (`npx dbdock backup`)
700
+ - Configuration is read from `dbdock.config.json` automatically
701
+ - Multiple recipients supported in the `to` array for email
702
+ - Alerts are sent asynchronously (won't block backup completion)
491
703
 
492
704
  ## Requirements
493
705
 
@@ -18,6 +18,7 @@ export declare class AlertService {
18
18
  }): Promise<void>;
19
19
  sendStorageErrorAlert(error: Error): Promise<void>;
20
20
  private sendAlert;
21
+ private sendSlackAlert;
21
22
  private sendEmail;
22
23
  verifyConnection(): Promise<boolean>;
23
24
  }
@@ -41,6 +41,9 @@ var __importStar = (this && this.__importStar) || (function () {
41
41
  var __metadata = (this && this.__metadata) || function (k, v) {
42
42
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
43
  };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
44
47
  var AlertService_1;
45
48
  Object.defineProperty(exports, "__esModule", { value: true });
46
49
  exports.AlertService = void 0;
@@ -49,6 +52,7 @@ const nodemailer = __importStar(require("nodemailer"));
49
52
  const config_service_1 = require("../config/config.service");
50
53
  const alert_types_1 = require("./alert.types");
51
54
  const alert_templates_1 = require("./alert-templates");
55
+ const node_fetch_1 = __importDefault(require("node-fetch"));
52
56
  let AlertService = AlertService_1 = class AlertService {
53
57
  configService;
54
58
  logger = new common_1.Logger(AlertService_1.name);
@@ -61,23 +65,28 @@ let AlertService = AlertService_1 = class AlertService {
61
65
  initializeTransporter() {
62
66
  const alertsConfig = this.configService.get('alerts');
63
67
  if (!alertsConfig) {
64
- this.logger.log('Email alerts disabled - no alerts configuration found');
68
+ this.logger.log('Alerts disabled - no alerts configuration found');
65
69
  return;
66
70
  }
67
- try {
68
- this.transporter = nodemailer.createTransport({
69
- host: alertsConfig.smtpHost,
70
- port: alertsConfig.smtpPort,
71
- secure: alertsConfig.smtpPort === 465,
72
- auth: {
73
- user: alertsConfig.smtpUser,
74
- pass: alertsConfig.smtpPass,
75
- },
76
- });
77
- this.logger.log(`Email alerts enabled - configured for ${alertsConfig.smtpHost}:${alertsConfig.smtpPort}`);
71
+ if (alertsConfig.smtpHost) {
72
+ try {
73
+ this.transporter = nodemailer.createTransport({
74
+ host: alertsConfig.smtpHost,
75
+ port: alertsConfig.smtpPort,
76
+ secure: alertsConfig.smtpPort === 465,
77
+ auth: {
78
+ user: alertsConfig.smtpUser,
79
+ pass: alertsConfig.smtpPass,
80
+ },
81
+ });
82
+ this.logger.log(`Email alerts enabled - configured for ${alertsConfig.smtpHost}:${alertsConfig.smtpPort}`);
83
+ }
84
+ catch (error) {
85
+ this.logger.error(`Failed to initialize email transporter: ${error.message}`);
86
+ }
78
87
  }
79
- catch (error) {
80
- this.logger.error(`Failed to initialize email transporter: ${error.message}`);
88
+ if (alertsConfig.slackWebhook) {
89
+ this.logger.log('Slack alerts enabled');
81
90
  }
82
91
  }
83
92
  setCustomTemplate(type, template) {
@@ -85,8 +94,6 @@ let AlertService = AlertService_1 = class AlertService {
85
94
  this.logger.log(`Custom template set for alert type: ${type}`);
86
95
  }
87
96
  async sendBackupSuccessAlert(metadata, downloadUrl) {
88
- if (!this.transporter)
89
- return;
90
97
  const context = {
91
98
  database: metadata.database,
92
99
  backupId: metadata.id,
@@ -104,8 +111,6 @@ let AlertService = AlertService_1 = class AlertService {
104
111
  });
105
112
  }
106
113
  async sendBackupFailureAlert(metadata, error) {
107
- if (!this.transporter)
108
- return;
109
114
  const context = {
110
115
  database: metadata.database,
111
116
  backupId: metadata.id,
@@ -120,8 +125,6 @@ let AlertService = AlertService_1 = class AlertService {
120
125
  });
121
126
  }
122
127
  async sendRetentionCleanupAlert(details) {
123
- if (!this.transporter)
124
- return;
125
128
  const context = {
126
129
  backupsDeleted: details.backupsDeleted,
127
130
  walFilesDeleted: details.walFilesDeleted,
@@ -134,8 +137,6 @@ let AlertService = AlertService_1 = class AlertService {
134
137
  });
135
138
  }
136
139
  async sendStorageErrorAlert(error) {
137
- if (!this.transporter)
138
- return;
139
140
  const context = {
140
141
  error: error.message,
141
142
  timestamp: new Date().toLocaleString(),
@@ -147,27 +148,88 @@ let AlertService = AlertService_1 = class AlertService {
147
148
  });
148
149
  }
149
150
  async sendAlert(alertContext) {
150
- if (!this.transporter)
151
- return;
152
151
  const alertsConfig = this.configService.get('alerts');
153
152
  if (!alertsConfig)
154
153
  return;
155
- try {
156
- const template = this.customTemplates[alertContext.type] ||
157
- alert_templates_1.DEFAULT_TEMPLATES[alertContext.type];
158
- const subject = (0, alert_templates_1.renderTemplate)(template.subject, alertContext.details || {});
159
- const html = (0, alert_templates_1.renderTemplate)(template.body, alertContext.details || {});
160
- const text = html.replace(/<[^>]*>/g, '');
161
- await this.sendEmail({
162
- to: alertsConfig.to,
163
- subject,
164
- html,
165
- text,
166
- });
167
- this.logger.log(`Alert sent: ${alertContext.type} to ${alertsConfig.to.join(', ')}`);
154
+ if (this.transporter && alertsConfig.to) {
155
+ try {
156
+ const template = this.customTemplates[alertContext.type] ||
157
+ alert_templates_1.DEFAULT_TEMPLATES[alertContext.type];
158
+ const subject = (0, alert_templates_1.renderTemplate)(template.subject, alertContext.details || {});
159
+ const html = (0, alert_templates_1.renderTemplate)(template.body, alertContext.details || {});
160
+ const text = html.replace(/<[^>]*>/g, '');
161
+ await this.sendEmail({
162
+ to: alertsConfig.to,
163
+ subject,
164
+ html,
165
+ text,
166
+ });
167
+ this.logger.log(`Email alert sent: ${alertContext.type} to ${alertsConfig.to.join(', ')}`);
168
+ }
169
+ catch (error) {
170
+ this.logger.error(`Failed to send email alert (${alertContext.type}): ${error.message}`);
171
+ }
168
172
  }
169
- catch (error) {
170
- this.logger.error(`Failed to send alert (${alertContext.type}): ${error.message}`);
173
+ if (alertsConfig.slackWebhook) {
174
+ try {
175
+ await this.sendSlackAlert(alertsConfig.slackWebhook, alertContext);
176
+ this.logger.log(`Slack alert sent: ${alertContext.type}`);
177
+ }
178
+ catch (error) {
179
+ this.logger.error(`Failed to send Slack alert (${alertContext.type}): ${error.message}`);
180
+ }
181
+ }
182
+ }
183
+ async sendSlackAlert(webhookUrl, context) {
184
+ let color = '#36a64f';
185
+ let title = 'DBDock Alert';
186
+ let text = '';
187
+ switch (context.type) {
188
+ case alert_types_1.AlertType.BACKUP_SUCCESS:
189
+ title = '✅ Backup Successful';
190
+ text = `Database: *${context.details?.database}*\nSize: ${context.details?.size} MB\nDuration: ${context.details?.duration}s`;
191
+ break;
192
+ case alert_types_1.AlertType.BACKUP_FAILURE:
193
+ color = '#dc3545';
194
+ title = '❌ Backup Failed';
195
+ text = `Database: *${context.details?.database}*\nError: ${context.details?.error}`;
196
+ break;
197
+ case alert_types_1.AlertType.RETENTION_CLEANUP:
198
+ color = '#17a2b8';
199
+ title = '🧹 Retention Cleanup';
200
+ text = `Deleted ${context.details?.backupsDeleted} backups\nFreed ${context.details?.spaceFreed} MB`;
201
+ break;
202
+ case alert_types_1.AlertType.STORAGE_ERROR:
203
+ color = '#ffc107';
204
+ title = '⚠️ Storage Error';
205
+ text = `Error: ${context.details?.error}`;
206
+ break;
207
+ }
208
+ const payload = {
209
+ attachments: [
210
+ {
211
+ color,
212
+ title,
213
+ text,
214
+ fields: Object.entries(context.details || {})
215
+ .filter(([key]) => !['database', 'size', 'duration', 'error', 'backupsDeleted', 'spaceFreed'].includes(key))
216
+ .map(([key, value]) => ({
217
+ title: key.charAt(0).toUpperCase() + key.slice(1),
218
+ value: String(value),
219
+ short: true,
220
+ })),
221
+ footer: 'DBDock',
222
+ ts: Math.floor(Date.now() / 1000),
223
+ },
224
+ ],
225
+ };
226
+ const response = await (0, node_fetch_1.default)(webhookUrl, {
227
+ method: 'POST',
228
+ headers: { 'Content-Type': 'application/json' },
229
+ body: JSON.stringify(payload),
230
+ });
231
+ if (!response.ok) {
232
+ throw new Error(`Slack API error: ${response.statusText}`);
171
233
  }
172
234
  }
173
235
  async sendEmail(options) {