Files
BackupX/server/internal/repository/backup_task_repository.go
Awuqing 09698cc767 feat: add community enhancements — password reset, audit logs, multi-source backup
Three community-requested features:

1. CLI password reset: `backupx reset-password --username admin --password xxx`
   Docker users can run via `docker exec`. No full app init needed.

2. Audit logging: async fire-and-forget audit trail for all key operations
   (login, CRUD on tasks/targets/records, settings changes).
   New UI page at /audit with category filter and pagination.

3. Multi-source path backup: file backup tasks now support multiple source
   directories packed into a single tar archive. Backward compatible with
   existing single sourcePath field.
2026-03-30 23:04:37 +08:00

138 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"context"
"errors"
"backupx/server/internal/model"
"gorm.io/gorm"
)
type BackupTaskListOptions struct {
Type string
Enabled *bool
}
type BackupTaskRepository interface {
List(context.Context, BackupTaskListOptions) ([]model.BackupTask, error)
FindByID(context.Context, uint) (*model.BackupTask, error)
FindByName(context.Context, string) (*model.BackupTask, error)
ListSchedulable(context.Context) ([]model.BackupTask, error)
Count(context.Context) (int64, error)
CountEnabled(context.Context) (int64, error)
CountByStorageTargetID(context.Context, uint) (int64, error)
Create(context.Context, *model.BackupTask) error
Update(context.Context, *model.BackupTask) error
Delete(context.Context, uint) error
}
type GormBackupTaskRepository struct {
db *gorm.DB
}
func NewBackupTaskRepository(db *gorm.DB) *GormBackupTaskRepository {
return &GormBackupTaskRepository{db: db}
}
func (r *GormBackupTaskRepository) List(ctx context.Context, options BackupTaskListOptions) ([]model.BackupTask, error) {
query := r.db.WithContext(ctx).Model(&model.BackupTask{}).Preload("StorageTarget").Preload("StorageTargets").Order("updated_at desc")
if options.Type != "" {
query = query.Where("type = ?", options.Type)
}
if options.Enabled != nil {
query = query.Where("enabled = ?", *options.Enabled)
}
var items []model.BackupTask
if err := query.Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
func (r *GormBackupTaskRepository) FindByID(ctx context.Context, id uint) (*model.BackupTask, error) {
var item model.BackupTask
if err := r.db.WithContext(ctx).Preload("StorageTarget").Preload("StorageTargets").First(&item, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &item, nil
}
func (r *GormBackupTaskRepository) FindByName(ctx context.Context, name string) (*model.BackupTask, error) {
var item model.BackupTask
if err := r.db.WithContext(ctx).Where("name = ?", name).First(&item).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &item, nil
}
func (r *GormBackupTaskRepository) ListSchedulable(ctx context.Context) ([]model.BackupTask, error) {
var items []model.BackupTask
if err := r.db.WithContext(ctx).Preload("StorageTarget").Preload("StorageTargets").Where("enabled = ? AND cron_expr <> ''", true).Order("id asc").Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
func (r *GormBackupTaskRepository) Count(ctx context.Context) (int64, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&model.BackupTask{}).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (r *GormBackupTaskRepository) CountEnabled(ctx context.Context) (int64, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&model.BackupTask{}).Where("enabled = ?", true).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (r *GormBackupTaskRepository) CountByStorageTargetID(ctx context.Context, storageTargetID uint) (int64, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&model.BackupTaskStorageTarget{}).Where("storage_target_id = ?", storageTargetID).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (r *GormBackupTaskRepository) Create(ctx context.Context, item *model.BackupTask) error {
if err := r.db.WithContext(ctx).Create(item).Error; err != nil {
return err
}
return r.syncStorageTargets(ctx, item)
}
func (r *GormBackupTaskRepository) Update(ctx context.Context, item *model.BackupTask) error {
if err := r.db.WithContext(ctx).Save(item).Error; err != nil {
return err
}
if len(item.StorageTargets) > 0 {
return r.db.WithContext(ctx).Model(item).Association("StorageTargets").Replace(item.StorageTargets)
}
return nil
}
// syncStorageTargets 确保中间表数据一致:优先使用 StorageTargets回退到 StorageTargetID
func (r *GormBackupTaskRepository) syncStorageTargets(ctx context.Context, item *model.BackupTask) error {
targets := item.StorageTargets
if len(targets) == 0 && item.StorageTargetID > 0 {
targets = []model.StorageTarget{{ID: item.StorageTargetID}}
}
if len(targets) > 0 {
return r.db.WithContext(ctx).Model(item).Association("StorageTargets").Replace(targets)
}
return nil
}
func (r *GormBackupTaskRepository) Delete(ctx context.Context, id uint) error {
return r.db.WithContext(ctx).Delete(&model.BackupTask{}, id).Error
}