feat: implement DatabaseBackupService with list & download API and settings UI#159
Conversation
🤖 Augment PR SummarySummary: Adds end-to-end SQLite database backup support, including server-side backup creation/listing/downloading and a settings UI to configure and access backups. Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
| Path backupPath; | ||
| try { | ||
| backupPath = databaseBackupService.resolveBackupPath(fileName); | ||
| } catch (IllegalStateException e) { |
There was a problem hiding this comment.
app/server/huntly-server/src/main/java/com/huntly/server/controller/SettingController.java:237 — resolveBackupPath() can throw IllegalArgumentException for invalid fileName, but this handler only catches IllegalStateException, so invalid input may surface as a 500 instead of a controlled 4xx response.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| return ResponseEntity.ok() | ||
| .contentType(MediaType.APPLICATION_OCTET_STREAM) | ||
| .contentLength(resource.contentLength()) | ||
| .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + backupPath.getFileName() + "\"") |
There was a problem hiding this comment.
app/server/huntly-server/src/main/java/com/huntly/server/controller/SettingController.java:248 — The Content-Disposition header is built from a raw filename string; if a backup file name contains quotes/control characters this can break the header or enable header-injection style issues.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| return; | ||
| private boolean shouldBackup(LocalDateTime now, LocalTime backupTime) { | ||
| LocalTime currentTime = now.toLocalTime(); | ||
| if (!currentTime.equals(backupTime)) { |
There was a problem hiding this comment.
app/server/huntly-server/src/main/java/com/huntly/server/task/DatabaseBackupTask.java:62 — shouldBackup() requires currentTime.equals(backupTime), so if the app is down or the scheduler runs late (past that minute) the daily backup will be skipped until the next day.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
|
|
||
| public static final Integer DEFAULT_COLD_DATA_KEEP_DAYS = 60; | ||
|
|
||
| public static final Integer DEFAULT_BACKUP_KEEP_COUNT = 3; |
There was a problem hiding this comment.
app/server/huntly-server/src/main/java/com/huntly/server/domain/constant/AppConstants.java:20 — PR description mentions a default keep-count of 5, but DEFAULT_BACKUP_KEEP_COUNT is set to 3 here; consider aligning the docs/expectations with the implemented default.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Summary
Builds on the database backup configuration foundation to deliver the full backup feature end-to-end.
Server
DatabaseBackupService(new)backupDatabase()— Uses SQLite'sVACUUM INTOstatement to create a point-in-time snapshot to the configured backup directory. Skips silently when backup is disabled or no path is configured.listBackups()— Returns metadata (file name, creation time, size) for all existing backup files in the directory.resolveBackupPath(fileName)— Validates file name against path-traversal attacks (../, absolute separators) and checks the resolved path stays inside the backup directory before returning it.cleanExcessBackups()— Deletes the oldest backups once thebackupKeepCountlimit is exceeded.DatabaseBackupInfoDTO (new)Lombok
@DatacarryingfileName,createdAt(Instant),sizeBytes.SettingControlleradditionsGET /api/setting/general/database-backups— lists all available backup files.GET /api/setting/general/database-backups/download?fileName=…— streams the backup file as an attachment withContent-Dispositionheader.DatabaseBackupTaskrefactorSimplified scheduling logic; delegates all backup work to
DatabaseBackupService.GlobalSetting/GlobalSettingServiceAdded
backupKeepCountfield and service method to expose the keep-count default.AppConstantsAdded
DEFAULT_BACKUP_KEEP_COUNT = 5.Client
databaseBackup.ts(new)Typed axios helpers:
fetchDatabaseBackups()— calls the list endpoint and returnsDatabaseBackupInfo[].getDatabaseBackupDownloadUrl(fileName)— builds the download URL with encoded file name.GeneralSettingcomponentAdded a Database Backup panel in the settings modal showing:
i18n
Added EN/ZH-CN translation keys for all new backup UI strings.
Misc client improvements
Search.tsx: quick-search suggestion chips; selected page ID persisted in URL (?p=).Twitter.tsx,SubHeader.tsx,PageFilters.tsx,SearchBox.tsx: minor fixes surfaced during development.Tests
DatabaseBackupServiceTest(new, 151 lines)backupDatabase,listBackups,resolveBackupPathincl. path-traversal rejectionDatabaseBackupTaskScheduleTest(new, 106 lines)GlobalSettingServiceTestDatabaseBackupTaskTestSecurity notes