mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-07-12 07:52:29 +08:00
first commit
This commit is contained in:
183
server/internal/repository/backup_record_repository.go
Normal file
183
server/internal/repository/backup_record_repository.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"backupx/server/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BackupRecordListOptions struct {
|
||||
TaskID *uint
|
||||
Status string
|
||||
DateFrom *time.Time
|
||||
DateTo *time.Time
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type BackupTimelinePoint struct {
|
||||
Date string `json:"date"`
|
||||
Total int64 `json:"total"`
|
||||
Success int64 `json:"success"`
|
||||
Failed int64 `json:"failed"`
|
||||
}
|
||||
|
||||
type BackupStorageUsageItem struct {
|
||||
StorageTargetID uint `json:"storageTargetId"`
|
||||
TotalSize int64 `json:"totalSize"`
|
||||
}
|
||||
|
||||
type BackupRecordRepository interface {
|
||||
List(context.Context, BackupRecordListOptions) ([]model.BackupRecord, error)
|
||||
FindByID(context.Context, uint) (*model.BackupRecord, error)
|
||||
Create(context.Context, *model.BackupRecord) error
|
||||
Update(context.Context, *model.BackupRecord) error
|
||||
Delete(context.Context, uint) error
|
||||
ListRecent(context.Context, int) ([]model.BackupRecord, error)
|
||||
ListSuccessfulByTask(context.Context, uint) ([]model.BackupRecord, error)
|
||||
Count(context.Context) (int64, error)
|
||||
CountSince(context.Context, time.Time) (int64, error)
|
||||
CountSuccessSince(context.Context, time.Time) (int64, error)
|
||||
SumFileSize(context.Context) (int64, error)
|
||||
TimelineSince(context.Context, time.Time) ([]BackupTimelinePoint, error)
|
||||
StorageUsage(context.Context) ([]BackupStorageUsageItem, error)
|
||||
}
|
||||
|
||||
type GormBackupRecordRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewBackupRecordRepository(db *gorm.DB) *GormBackupRecordRepository {
|
||||
return &GormBackupRecordRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) List(ctx context.Context, options BackupRecordListOptions) ([]model.BackupRecord, error) {
|
||||
query := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Preload("Task").Preload("Task.StorageTarget").Order("started_at desc")
|
||||
if options.TaskID != nil {
|
||||
query = query.Where("task_id = ?", *options.TaskID)
|
||||
}
|
||||
if options.Status != "" {
|
||||
query = query.Where("status = ?", options.Status)
|
||||
}
|
||||
if options.DateFrom != nil {
|
||||
query = query.Where("started_at >= ?", options.DateFrom.UTC())
|
||||
}
|
||||
if options.DateTo != nil {
|
||||
query = query.Where("started_at <= ?", options.DateTo.UTC())
|
||||
}
|
||||
if options.Limit > 0 {
|
||||
query = query.Limit(options.Limit)
|
||||
}
|
||||
if options.Offset > 0 {
|
||||
query = query.Offset(options.Offset)
|
||||
}
|
||||
var items []model.BackupRecord
|
||||
if err := query.Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) FindByID(ctx context.Context, id uint) (*model.BackupRecord, error) {
|
||||
var item model.BackupRecord
|
||||
if err := r.db.WithContext(ctx).Preload("Task").Preload("Task.StorageTarget").First(&item, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) Create(ctx context.Context, item *model.BackupRecord) error {
|
||||
return r.db.WithContext(ctx).Create(item).Error
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) Update(ctx context.Context, item *model.BackupRecord) error {
|
||||
return r.db.WithContext(ctx).Save(item).Error
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) Delete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&model.BackupRecord{}, id).Error
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) ListRecent(ctx context.Context, limit int) ([]model.BackupRecord, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
var items []model.BackupRecord
|
||||
if err := r.db.WithContext(ctx).Preload("Task").Preload("Task.StorageTarget").Order("started_at desc").Limit(limit).Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) ListSuccessfulByTask(ctx context.Context, taskID uint) ([]model.BackupRecord, error) {
|
||||
var items []model.BackupRecord
|
||||
if err := r.db.WithContext(ctx).Where("task_id = ? AND status = ?", taskID, "success").Order("completed_at desc, id desc").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) CountSince(ctx context.Context, since time.Time) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Where("started_at >= ?", since.UTC()).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) CountSuccessSince(ctx context.Context, since time.Time) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Where("started_at >= ? AND status = ?", since.UTC(), "success").Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) SumFileSize(ctx context.Context) (int64, error) {
|
||||
var sum int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Select("COALESCE(SUM(file_size), 0)").Scan(&sum).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) TimelineSince(ctx context.Context, since time.Time) ([]BackupTimelinePoint, error) {
|
||||
var items []BackupTimelinePoint
|
||||
query := `
|
||||
SELECT
|
||||
strftime('%Y-%m-%d', started_at) AS date,
|
||||
COUNT(*) AS total,
|
||||
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS success,
|
||||
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed
|
||||
FROM backup_records
|
||||
WHERE started_at >= ?
|
||||
GROUP BY strftime('%Y-%m-%d', started_at)
|
||||
ORDER BY date ASC
|
||||
`
|
||||
if err := r.db.WithContext(ctx).Raw(query, since.UTC()).Scan(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormBackupRecordRepository) StorageUsage(ctx context.Context) ([]BackupStorageUsageItem, error) {
|
||||
var items []BackupStorageUsageItem
|
||||
if err := r.db.WithContext(ctx).Model(&model.BackupRecord{}).Select("storage_target_id, COALESCE(SUM(file_size), 0) AS total_size").Group("storage_target_id").Order("storage_target_id asc").Scan(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
115
server/internal/repository/backup_record_repository_test.go
Normal file
115
server/internal/repository/backup_record_repository_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"backupx/server/internal/config"
|
||||
"backupx/server/internal/database"
|
||||
"backupx/server/internal/logger"
|
||||
"backupx/server/internal/model"
|
||||
)
|
||||
|
||||
func newBackupRecordTestRepository(t *testing.T) *GormBackupRecordRepository {
|
||||
t.Helper()
|
||||
log, err := logger.New(config.LogConfig{Level: "error"})
|
||||
if err != nil {
|
||||
t.Fatalf("logger.New returned error: %v", err)
|
||||
}
|
||||
db, err := database.Open(config.DatabaseConfig{Path: filepath.Join(t.TempDir(), "backupx.db")}, log)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open returned error: %v", err)
|
||||
}
|
||||
storageTarget := &model.StorageTarget{Name: "local", Type: "local_disk", Enabled: true, ConfigCiphertext: "{}", ConfigVersion: 1, LastTestStatus: "unknown"}
|
||||
if err := db.Create(storageTarget).Error; err != nil {
|
||||
t.Fatalf("seed storage target error: %v", err)
|
||||
}
|
||||
task := &model.BackupTask{Name: "website", Type: "file", Enabled: true, SourcePath: "/srv/www/site", StorageTargetID: storageTarget.ID, RetentionDays: 30, Compression: "gzip", MaxBackups: 10, LastStatus: "idle"}
|
||||
if err := db.Create(task).Error; err != nil {
|
||||
t.Fatalf("seed backup task error: %v", err)
|
||||
}
|
||||
return NewBackupRecordRepository(db)
|
||||
}
|
||||
|
||||
func TestBackupRecordRepositoryQueries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := newBackupRecordTestRepository(t)
|
||||
now := time.Now().UTC()
|
||||
completedAt := now.Add(2 * time.Minute)
|
||||
record := &model.BackupRecord{
|
||||
TaskID: 1,
|
||||
StorageTargetID: 1,
|
||||
Status: "success",
|
||||
FileName: "website.tar.gz",
|
||||
FileSize: 1024,
|
||||
StoragePath: "tasks/1/website.tar.gz",
|
||||
DurationSeconds: 120,
|
||||
LogContent: "done",
|
||||
StartedAt: now,
|
||||
CompletedAt: &completedAt,
|
||||
}
|
||||
if err := repo.Create(ctx, record); err != nil {
|
||||
t.Fatalf("Create returned error: %v", err)
|
||||
}
|
||||
stored, err := repo.FindByID(ctx, record.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByID returned error: %v", err)
|
||||
}
|
||||
if stored == nil || stored.FileName != "website.tar.gz" {
|
||||
t.Fatalf("unexpected stored record: %#v", stored)
|
||||
}
|
||||
listed, err := repo.List(ctx, BackupRecordListOptions{TaskID: &record.TaskID, Status: "success"})
|
||||
if err != nil {
|
||||
t.Fatalf("List returned error: %v", err)
|
||||
}
|
||||
if len(listed) != 1 {
|
||||
t.Fatalf("expected one listed record, got %d", len(listed))
|
||||
}
|
||||
recent, err := repo.ListRecent(ctx, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("ListRecent returned error: %v", err)
|
||||
}
|
||||
if len(recent) != 1 {
|
||||
t.Fatalf("expected one recent record, got %d", len(recent))
|
||||
}
|
||||
total, err := repo.Count(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Count returned error: %v", err)
|
||||
}
|
||||
if total != 1 {
|
||||
t.Fatalf("expected total count 1, got %d", total)
|
||||
}
|
||||
successCount, err := repo.CountSuccessSince(ctx, now.Add(-time.Hour))
|
||||
if err != nil {
|
||||
t.Fatalf("CountSuccessSince returned error: %v", err)
|
||||
}
|
||||
if successCount != 1 {
|
||||
t.Fatalf("expected success count 1, got %d", successCount)
|
||||
}
|
||||
sum, err := repo.SumFileSize(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("SumFileSize returned error: %v", err)
|
||||
}
|
||||
if sum != 1024 {
|
||||
t.Fatalf("expected file size sum 1024, got %d", sum)
|
||||
}
|
||||
timeline, err := repo.TimelineSince(ctx, now.Add(-time.Hour))
|
||||
if err != nil {
|
||||
t.Fatalf("TimelineSince returned error: %v", err)
|
||||
}
|
||||
if len(timeline) != 1 || timeline[0].Success != 1 {
|
||||
t.Fatalf("unexpected timeline: %#v", timeline)
|
||||
}
|
||||
usage, err := repo.StorageUsage(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("StorageUsage returned error: %v", err)
|
||||
}
|
||||
if len(usage) != 1 || usage[0].TotalSize != 1024 {
|
||||
t.Fatalf("unexpected usage: %#v", usage)
|
||||
}
|
||||
if err := repo.Delete(ctx, record.ID); err != nil {
|
||||
t.Fatalf("Delete returned error: %v", err)
|
||||
}
|
||||
}
|
||||
116
server/internal/repository/backup_task_repository.go
Normal file
116
server/internal/repository/backup_task_repository.go
Normal file
@@ -0,0 +1,116 @@
|
||||
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").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").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").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.BackupTask{}).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 {
|
||||
return r.db.WithContext(ctx).Create(item).Error
|
||||
}
|
||||
|
||||
func (r *GormBackupTaskRepository) Update(ctx context.Context, item *model.BackupTask) error {
|
||||
return r.db.WithContext(ctx).Save(item).Error
|
||||
}
|
||||
|
||||
func (r *GormBackupTaskRepository) Delete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&model.BackupTask{}, id).Error
|
||||
}
|
||||
94
server/internal/repository/backup_task_repository_test.go
Normal file
94
server/internal/repository/backup_task_repository_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"backupx/server/internal/config"
|
||||
"backupx/server/internal/database"
|
||||
"backupx/server/internal/logger"
|
||||
"backupx/server/internal/model"
|
||||
)
|
||||
|
||||
func newBackupTaskTestRepository(t *testing.T) *GormBackupTaskRepository {
|
||||
t.Helper()
|
||||
log, err := logger.New(config.LogConfig{Level: "error"})
|
||||
if err != nil {
|
||||
t.Fatalf("logger.New returned error: %v", err)
|
||||
}
|
||||
db, err := database.Open(config.DatabaseConfig{Path: filepath.Join(t.TempDir(), "backupx.db")}, log)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open returned error: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.StorageTarget{Name: "local", Type: "local_disk", Enabled: true, ConfigCiphertext: "{}", ConfigVersion: 1, LastTestStatus: "unknown"}).Error; err != nil {
|
||||
t.Fatalf("seed storage target error: %v", err)
|
||||
}
|
||||
return NewBackupTaskRepository(db)
|
||||
}
|
||||
|
||||
func TestBackupTaskRepositoryCRUD(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := newBackupTaskTestRepository(t)
|
||||
task := &model.BackupTask{
|
||||
Name: "website",
|
||||
Type: "file",
|
||||
Enabled: true,
|
||||
SourcePath: "/srv/www/site",
|
||||
StorageTargetID: 1,
|
||||
RetentionDays: 30,
|
||||
Compression: "gzip",
|
||||
MaxBackups: 10,
|
||||
LastStatus: "idle",
|
||||
}
|
||||
if err := repo.Create(ctx, task); err != nil {
|
||||
t.Fatalf("Create returned error: %v", err)
|
||||
}
|
||||
stored, err := repo.FindByID(ctx, task.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByID returned error: %v", err)
|
||||
}
|
||||
if stored == nil || stored.Name != "website" {
|
||||
t.Fatalf("unexpected stored task: %#v", stored)
|
||||
}
|
||||
stored.Enabled = false
|
||||
stored.CronExpr = "0 3 * * *"
|
||||
if err := repo.Update(ctx, stored); err != nil {
|
||||
t.Fatalf("Update returned error: %v", err)
|
||||
}
|
||||
schedulable, err := repo.ListSchedulable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ListSchedulable returned error: %v", err)
|
||||
}
|
||||
if len(schedulable) != 0 {
|
||||
t.Fatalf("expected disabled task not schedulable, got %d", len(schedulable))
|
||||
}
|
||||
stored.Enabled = true
|
||||
if err := repo.Update(ctx, stored); err != nil {
|
||||
t.Fatalf("Update returned error: %v", err)
|
||||
}
|
||||
schedulable, err = repo.ListSchedulable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ListSchedulable returned error: %v", err)
|
||||
}
|
||||
if len(schedulable) != 1 {
|
||||
t.Fatalf("expected one schedulable task, got %d", len(schedulable))
|
||||
}
|
||||
count, err := repo.CountByStorageTargetID(ctx, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("CountByStorageTargetID returned error: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("expected referenced task count 1, got %d", count)
|
||||
}
|
||||
if err := repo.Delete(ctx, task.ID); err != nil {
|
||||
t.Fatalf("Delete returned error: %v", err)
|
||||
}
|
||||
deleted, err := repo.FindByID(ctx, task.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByID after delete returned error: %v", err)
|
||||
}
|
||||
if deleted != nil {
|
||||
t.Fatalf("expected task deleted, got %#v", deleted)
|
||||
}
|
||||
}
|
||||
80
server/internal/repository/node_repository.go
Normal file
80
server/internal/repository/node_repository.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"backupx/server/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type NodeRepository interface {
|
||||
List(context.Context) ([]model.Node, error)
|
||||
FindByID(context.Context, uint) (*model.Node, error)
|
||||
FindByToken(context.Context, string) (*model.Node, error)
|
||||
FindLocal(context.Context) (*model.Node, error)
|
||||
Create(context.Context, *model.Node) error
|
||||
Update(context.Context, *model.Node) error
|
||||
Delete(context.Context, uint) error
|
||||
}
|
||||
|
||||
type GormNodeRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewNodeRepository(db *gorm.DB) *GormNodeRepository {
|
||||
return &GormNodeRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *GormNodeRepository) List(ctx context.Context) ([]model.Node, error) {
|
||||
var items []model.Node
|
||||
if err := r.db.WithContext(ctx).Order("is_local desc, updated_at desc").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormNodeRepository) FindByID(ctx context.Context, id uint) (*model.Node, error) {
|
||||
var item model.Node
|
||||
if err := r.db.WithContext(ctx).First(&item, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormNodeRepository) FindByToken(ctx context.Context, token string) (*model.Node, error) {
|
||||
var item model.Node
|
||||
if err := r.db.WithContext(ctx).Where("token = ?", token).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormNodeRepository) FindLocal(ctx context.Context) (*model.Node, error) {
|
||||
var item model.Node
|
||||
if err := r.db.WithContext(ctx).Where("is_local = ?", true).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormNodeRepository) Create(ctx context.Context, item *model.Node) error {
|
||||
return r.db.WithContext(ctx).Create(item).Error
|
||||
}
|
||||
|
||||
func (r *GormNodeRepository) Update(ctx context.Context, item *model.Node) error {
|
||||
return r.db.WithContext(ctx).Save(item).Error
|
||||
}
|
||||
|
||||
func (r *GormNodeRepository) Delete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&model.Node{}, id).Error
|
||||
}
|
||||
83
server/internal/repository/notification_repository.go
Normal file
83
server/internal/repository/notification_repository.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"backupx/server/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type NotificationRepository interface {
|
||||
List(context.Context) ([]model.Notification, error)
|
||||
ListEnabledForEvent(context.Context, bool) ([]model.Notification, error)
|
||||
FindByID(context.Context, uint) (*model.Notification, error)
|
||||
FindByName(context.Context, string) (*model.Notification, error)
|
||||
Create(context.Context, *model.Notification) error
|
||||
Update(context.Context, *model.Notification) error
|
||||
Delete(context.Context, uint) error
|
||||
}
|
||||
|
||||
type GormNotificationRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewNotificationRepository(db *gorm.DB) *GormNotificationRepository {
|
||||
return &GormNotificationRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *GormNotificationRepository) List(ctx context.Context) ([]model.Notification, error) {
|
||||
var items []model.Notification
|
||||
if err := r.db.WithContext(ctx).Order("updated_at desc").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormNotificationRepository) ListEnabledForEvent(ctx context.Context, success bool) ([]model.Notification, error) {
|
||||
query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("enabled = ?", true)
|
||||
if success {
|
||||
query = query.Where("on_success = ?", true)
|
||||
} else {
|
||||
query = query.Where("on_failure = ?", true)
|
||||
}
|
||||
var items []model.Notification
|
||||
if err := query.Order("updated_at desc").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormNotificationRepository) FindByID(ctx context.Context, id uint) (*model.Notification, error) {
|
||||
var item model.Notification
|
||||
if err := r.db.WithContext(ctx).First(&item, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormNotificationRepository) FindByName(ctx context.Context, name string) (*model.Notification, error) {
|
||||
var item model.Notification
|
||||
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 *GormNotificationRepository) Create(ctx context.Context, item *model.Notification) error {
|
||||
return r.db.WithContext(ctx).Create(item).Error
|
||||
}
|
||||
|
||||
func (r *GormNotificationRepository) Update(ctx context.Context, item *model.Notification) error {
|
||||
return r.db.WithContext(ctx).Save(item).Error
|
||||
}
|
||||
|
||||
func (r *GormNotificationRepository) Delete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&model.Notification{}, id).Error
|
||||
}
|
||||
69
server/internal/repository/notification_repository_test.go
Normal file
69
server/internal/repository/notification_repository_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"backupx/server/internal/config"
|
||||
"backupx/server/internal/database"
|
||||
"backupx/server/internal/logger"
|
||||
"backupx/server/internal/model"
|
||||
)
|
||||
|
||||
func newNotificationTestRepository(t *testing.T) *GormNotificationRepository {
|
||||
t.Helper()
|
||||
log, err := logger.New(config.LogConfig{Level: "error"})
|
||||
if err != nil {
|
||||
t.Fatalf("logger.New returned error: %v", err)
|
||||
}
|
||||
db, err := database.Open(config.DatabaseConfig{Path: filepath.Join(t.TempDir(), "backupx.db")}, log)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open returned error: %v", err)
|
||||
}
|
||||
return NewNotificationRepository(db)
|
||||
}
|
||||
|
||||
func TestNotificationRepositoryCRUD(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := newNotificationTestRepository(t)
|
||||
item := &model.Notification{
|
||||
Type: "webhook",
|
||||
Name: "ops-webhook",
|
||||
ConfigCiphertext: "ciphertext",
|
||||
Enabled: true,
|
||||
OnSuccess: false,
|
||||
OnFailure: true,
|
||||
}
|
||||
if err := repo.Create(ctx, item); err != nil {
|
||||
t.Fatalf("Create returned error: %v", err)
|
||||
}
|
||||
stored, err := repo.FindByName(ctx, "ops-webhook")
|
||||
if err != nil {
|
||||
t.Fatalf("FindByName returned error: %v", err)
|
||||
}
|
||||
if stored == nil || stored.Name != "ops-webhook" {
|
||||
t.Fatalf("unexpected notification: %#v", stored)
|
||||
}
|
||||
enabledForFailure, err := repo.ListEnabledForEvent(ctx, false)
|
||||
if err != nil {
|
||||
t.Fatalf("ListEnabledForEvent returned error: %v", err)
|
||||
}
|
||||
if len(enabledForFailure) != 1 {
|
||||
t.Fatalf("expected one failure notification, got %d", len(enabledForFailure))
|
||||
}
|
||||
stored.OnSuccess = true
|
||||
if err := repo.Update(ctx, stored); err != nil {
|
||||
t.Fatalf("Update returned error: %v", err)
|
||||
}
|
||||
enabledForSuccess, err := repo.ListEnabledForEvent(ctx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ListEnabledForEvent returned error: %v", err)
|
||||
}
|
||||
if len(enabledForSuccess) != 1 {
|
||||
t.Fatalf("expected one success notification, got %d", len(enabledForSuccess))
|
||||
}
|
||||
if err := repo.Delete(ctx, item.ID); err != nil {
|
||||
t.Fatalf("Delete returned error: %v", err)
|
||||
}
|
||||
}
|
||||
48
server/internal/repository/oauth_session_repository.go
Normal file
48
server/internal/repository/oauth_session_repository.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"backupx/server/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type OAuthSessionRepository interface {
|
||||
Create(context.Context, *model.OAuthSession) error
|
||||
Update(context.Context, *model.OAuthSession) error
|
||||
FindByState(context.Context, string) (*model.OAuthSession, error)
|
||||
DeleteExpired(context.Context, time.Time) error
|
||||
}
|
||||
|
||||
type GormOAuthSessionRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewOAuthSessionRepository(db *gorm.DB) *GormOAuthSessionRepository {
|
||||
return &GormOAuthSessionRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *GormOAuthSessionRepository) Create(ctx context.Context, item *model.OAuthSession) error {
|
||||
return r.db.WithContext(ctx).Create(item).Error
|
||||
}
|
||||
|
||||
func (r *GormOAuthSessionRepository) Update(ctx context.Context, item *model.OAuthSession) error {
|
||||
return r.db.WithContext(ctx).Save(item).Error
|
||||
}
|
||||
|
||||
func (r *GormOAuthSessionRepository) FindByState(ctx context.Context, state string) (*model.OAuthSession, error) {
|
||||
var item model.OAuthSession
|
||||
if err := r.db.WithContext(ctx).Where("state = ?", state).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormOAuthSessionRepository) DeleteExpired(ctx context.Context, before time.Time) error {
|
||||
return r.db.WithContext(ctx).Where("expires_at <= ?", before).Delete(&model.OAuthSession{}).Error
|
||||
}
|
||||
73
server/internal/repository/oauth_session_repository_test.go
Normal file
73
server/internal/repository/oauth_session_repository_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"backupx/server/internal/config"
|
||||
"backupx/server/internal/database"
|
||||
"backupx/server/internal/logger"
|
||||
"backupx/server/internal/model"
|
||||
)
|
||||
|
||||
func newOAuthSessionTestRepository(t *testing.T) *GormOAuthSessionRepository {
|
||||
t.Helper()
|
||||
log, err := logger.New(config.LogConfig{Level: "error"})
|
||||
if err != nil {
|
||||
t.Fatalf("logger.New returned error: %v", err)
|
||||
}
|
||||
db, err := database.Open(config.DatabaseConfig{Path: filepath.Join(t.TempDir(), "backupx.db")}, log)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open returned error: %v", err)
|
||||
}
|
||||
return NewOAuthSessionRepository(db)
|
||||
}
|
||||
|
||||
func TestOAuthSessionRepositoryCRUDAndDeleteExpired(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := newOAuthSessionTestRepository(t)
|
||||
expiresAt := time.Now().UTC().Add(5 * time.Minute)
|
||||
session := &model.OAuthSession{
|
||||
ProviderType: "google_drive",
|
||||
State: "oauth-state",
|
||||
PayloadCiphertext: "ciphertext",
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
if err := repo.Create(ctx, session); err != nil {
|
||||
t.Fatalf("Create returned error: %v", err)
|
||||
}
|
||||
stored, err := repo.FindByState(ctx, "oauth-state")
|
||||
if err != nil {
|
||||
t.Fatalf("FindByState returned error: %v", err)
|
||||
}
|
||||
if stored == nil || stored.State != "oauth-state" {
|
||||
t.Fatalf("unexpected stored session: %#v", stored)
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
stored.UsedAt = &now
|
||||
if err := repo.Update(ctx, stored); err != nil {
|
||||
t.Fatalf("Update returned error: %v", err)
|
||||
}
|
||||
if err := repo.DeleteExpired(ctx, time.Now().UTC().Add(-time.Minute)); err != nil {
|
||||
t.Fatalf("DeleteExpired returned error: %v", err)
|
||||
}
|
||||
stillThere, err := repo.FindByState(ctx, "oauth-state")
|
||||
if err != nil {
|
||||
t.Fatalf("FindByState after DeleteExpired returned error: %v", err)
|
||||
}
|
||||
if stillThere == nil {
|
||||
t.Fatalf("expected unexpired session to remain")
|
||||
}
|
||||
if err := repo.DeleteExpired(ctx, time.Now().UTC().Add(10*time.Minute)); err != nil {
|
||||
t.Fatalf("DeleteExpired returned error: %v", err)
|
||||
}
|
||||
deleted, err := repo.FindByState(ctx, "oauth-state")
|
||||
if err != nil {
|
||||
t.Fatalf("FindByState after expiration delete returned error: %v", err)
|
||||
}
|
||||
if deleted != nil {
|
||||
t.Fatalf("expected session to be deleted, got %#v", deleted)
|
||||
}
|
||||
}
|
||||
68
server/internal/repository/storage_target_repository.go
Normal file
68
server/internal/repository/storage_target_repository.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"backupx/server/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type StorageTargetRepository interface {
|
||||
List(context.Context) ([]model.StorageTarget, error)
|
||||
FindByID(context.Context, uint) (*model.StorageTarget, error)
|
||||
FindByName(context.Context, string) (*model.StorageTarget, error)
|
||||
Create(context.Context, *model.StorageTarget) error
|
||||
Update(context.Context, *model.StorageTarget) error
|
||||
Delete(context.Context, uint) error
|
||||
}
|
||||
|
||||
type GormStorageTargetRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewStorageTargetRepository(db *gorm.DB) *GormStorageTargetRepository {
|
||||
return &GormStorageTargetRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *GormStorageTargetRepository) List(ctx context.Context) ([]model.StorageTarget, error) {
|
||||
var items []model.StorageTarget
|
||||
if err := r.db.WithContext(ctx).Order("updated_at desc").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormStorageTargetRepository) FindByID(ctx context.Context, id uint) (*model.StorageTarget, error) {
|
||||
var item model.StorageTarget
|
||||
if err := r.db.WithContext(ctx).First(&item, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormStorageTargetRepository) FindByName(ctx context.Context, name string) (*model.StorageTarget, error) {
|
||||
var item model.StorageTarget
|
||||
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 *GormStorageTargetRepository) Create(ctx context.Context, item *model.StorageTarget) error {
|
||||
return r.db.WithContext(ctx).Create(item).Error
|
||||
}
|
||||
|
||||
func (r *GormStorageTargetRepository) Update(ctx context.Context, item *model.StorageTarget) error {
|
||||
return r.db.WithContext(ctx).Save(item).Error
|
||||
}
|
||||
|
||||
func (r *GormStorageTargetRepository) Delete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&model.StorageTarget{}, id).Error
|
||||
}
|
||||
81
server/internal/repository/storage_target_repository_test.go
Normal file
81
server/internal/repository/storage_target_repository_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"backupx/server/internal/config"
|
||||
"backupx/server/internal/database"
|
||||
"backupx/server/internal/logger"
|
||||
"backupx/server/internal/model"
|
||||
)
|
||||
|
||||
func openTestDB(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func newStorageTestRepository(t *testing.T) *GormStorageTargetRepository {
|
||||
t.Helper()
|
||||
log, err := logger.New(config.LogConfig{Level: "error"})
|
||||
if err != nil {
|
||||
t.Fatalf("logger.New returned error: %v", err)
|
||||
}
|
||||
db, err := database.Open(config.DatabaseConfig{Path: filepath.Join(t.TempDir(), "backupx.db")}, log)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open returned error: %v", err)
|
||||
}
|
||||
return NewStorageTargetRepository(db)
|
||||
}
|
||||
|
||||
func TestStorageTargetRepositoryCRUD(t *testing.T) {
|
||||
ctx := openTestDB(t)
|
||||
repo := newStorageTestRepository(t)
|
||||
item := &model.StorageTarget{
|
||||
Name: "local",
|
||||
Type: "local_disk",
|
||||
Enabled: true,
|
||||
ConfigCiphertext: "ciphertext",
|
||||
ConfigVersion: 1,
|
||||
LastTestStatus: "unknown",
|
||||
}
|
||||
if err := repo.Create(ctx, item); err != nil {
|
||||
t.Fatalf("Create returned error: %v", err)
|
||||
}
|
||||
stored, err := repo.FindByID(ctx, item.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByID returned error: %v", err)
|
||||
}
|
||||
if stored == nil || stored.Name != "local" {
|
||||
t.Fatalf("unexpected stored target: %#v", stored)
|
||||
}
|
||||
byName, err := repo.FindByName(ctx, "local")
|
||||
if err != nil {
|
||||
t.Fatalf("FindByName returned error: %v", err)
|
||||
}
|
||||
if byName == nil || byName.ID != item.ID {
|
||||
t.Fatalf("expected target lookup by name to match, got %#v", byName)
|
||||
}
|
||||
stored.Description = "updated"
|
||||
if err := repo.Update(ctx, stored); err != nil {
|
||||
t.Fatalf("Update returned error: %v", err)
|
||||
}
|
||||
items, err := repo.List(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("List returned error: %v", err)
|
||||
}
|
||||
if len(items) != 1 || items[0].Description != "updated" {
|
||||
t.Fatalf("unexpected list result: %#v", items)
|
||||
}
|
||||
if err := repo.Delete(ctx, item.ID); err != nil {
|
||||
t.Fatalf("Delete returned error: %v", err)
|
||||
}
|
||||
deleted, err := repo.FindByID(ctx, item.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByID after delete returned error: %v", err)
|
||||
}
|
||||
if deleted != nil {
|
||||
t.Fatalf("expected target to be deleted, got %#v", deleted)
|
||||
}
|
||||
}
|
||||
50
server/internal/repository/system_config_repository.go
Normal file
50
server/internal/repository/system_config_repository.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"backupx/server/internal/model"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type SystemConfigRepository interface {
|
||||
GetByKey(context.Context, string) (*model.SystemConfig, error)
|
||||
List(context.Context) ([]model.SystemConfig, error)
|
||||
Upsert(context.Context, *model.SystemConfig) error
|
||||
}
|
||||
|
||||
type GormSystemConfigRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSystemConfigRepository(db *gorm.DB) *GormSystemConfigRepository {
|
||||
return &GormSystemConfigRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *GormSystemConfigRepository) GetByKey(ctx context.Context, key string) (*model.SystemConfig, error) {
|
||||
var item model.SystemConfig
|
||||
if err := r.db.WithContext(ctx).Where("key = ?", key).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *GormSystemConfigRepository) List(ctx context.Context) ([]model.SystemConfig, error) {
|
||||
var items []model.SystemConfig
|
||||
if err := r.db.WithContext(ctx).Order("key ASC").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *GormSystemConfigRepository) Upsert(ctx context.Context, item *model.SystemConfig) error {
|
||||
return r.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "key"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"value", "encrypted", "updated_at"}),
|
||||
}).Create(item).Error
|
||||
}
|
||||
63
server/internal/repository/user_repository.go
Normal file
63
server/internal/repository/user_repository.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"backupx/server/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserRepository interface {
|
||||
Count(context.Context) (int64, error)
|
||||
Create(context.Context, *model.User) error
|
||||
Update(context.Context, *model.User) error
|
||||
FindByUsername(context.Context, string) (*model.User, error)
|
||||
FindByID(context.Context, uint) (*model.User, error)
|
||||
}
|
||||
|
||||
type GormUserRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) *GormUserRepository {
|
||||
return &GormUserRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *GormUserRepository) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.User{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r *GormUserRepository) Create(ctx context.Context, user *model.User) error {
|
||||
return r.db.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
|
||||
func (r *GormUserRepository) Update(ctx context.Context, user *model.User) error {
|
||||
return r.db.WithContext(ctx).Save(user).Error
|
||||
}
|
||||
|
||||
func (r *GormUserRepository) FindByUsername(ctx context.Context, username string) (*model.User, error) {
|
||||
var user model.User
|
||||
if err := r.db.WithContext(ctx).Where("username = ?", username).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *GormUserRepository) FindByID(ctx context.Context, id uint) (*model.User, error) {
|
||||
var user model.User
|
||||
if err := r.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
Reference in New Issue
Block a user