mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-02 04:16:38 +08:00
初始化
初始化项目
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@@ -54,106 +53,6 @@ func TestOpenSQLiteWithNilLoggerConfiguresPool(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnforceTelegramBindingOneToOneCleansDuplicatesAndAddsIndex(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.TelegramBinding{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createdAt := time.Now().Add(-time.Hour)
|
||||
rows := []model.TelegramBinding{
|
||||
{TelegramUserID: 10001, ChatID: 10001, UserID: "user-1"},
|
||||
{TelegramUserID: 10002, ChatID: 10002, UserID: "user-1"},
|
||||
}
|
||||
for i := range rows {
|
||||
rows[i].CreatedAt = createdAt.Add(time.Duration(i) * time.Minute)
|
||||
if err := db.Create(&rows[i]).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := enforceTelegramBindingOneToOne(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := db.Model(&model.TelegramBinding{}).Where("user_id = ?", "user-1").Count(&count).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("active bindings for user-1 = %d, want 1", count)
|
||||
}
|
||||
var kept model.TelegramBinding
|
||||
if err := db.First(&kept, "user_id = ?", "user-1").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if kept.TelegramUserID != 10002 {
|
||||
t.Fatalf("kept telegram binding = %d, want newest 10002", kept.TelegramUserID)
|
||||
}
|
||||
if err := db.Create(&model.TelegramBinding{TelegramUserID: 10003, ChatID: 10003, UserID: "user-1"}).Error; err == nil {
|
||||
t.Fatal("expected unique index to reject another active binding for the same user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureSubscriptionIdentityUniquenessArchivesDuplicatesAndAddsIndex(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Subscription{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createdAt := time.Now().Add(-time.Hour)
|
||||
rows := []model.Subscription{
|
||||
{UserID: "user-1", Name: "Example", FeedURL: "site-search://search?keyword=Example", Filter: "Example", Resolution: "1080p", Priority: 50},
|
||||
{UserID: "user-1", Name: "Example", FeedURL: "site-search://search?keyword=Example", Filter: "Example", Resolution: "1080p", Priority: 50},
|
||||
}
|
||||
for i := range rows {
|
||||
rows[i].CreatedAt = createdAt.Add(time.Duration(i) * time.Minute)
|
||||
if err := db.Select("*").Omit("DeletedAt").Create(&rows[i]).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := ensureSubscriptionIdentityUniqueness(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var active []model.Subscription
|
||||
if err := db.Where("archived_at IS NULL").Order("created_at asc").Find(&active).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(active) != 1 || active[0].ID != rows[0].ID {
|
||||
t.Fatalf("active subscriptions = %#v, want earliest row only", active)
|
||||
}
|
||||
var archived model.Subscription
|
||||
if err := db.First(&archived, "id = ?", rows[1].ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if archived.ArchivedAt == nil || archived.ArchiveReason != duplicateSubscriptionMigrationReason {
|
||||
t.Fatalf("duplicate was not archived by migration: %#v", archived)
|
||||
}
|
||||
|
||||
duplicate := rows[0]
|
||||
duplicate.ID = ""
|
||||
duplicate.CreatedAt = time.Time{}
|
||||
duplicate.UpdatedAt = time.Time{}
|
||||
model.RefreshSubscriptionIdentity(&duplicate)
|
||||
if err := db.Select("*").Omit("DeletedAt").Create(&duplicate).Error; err == nil {
|
||||
t.Fatal("expected active identity index to reject a duplicate rule")
|
||||
}
|
||||
different := rows[0]
|
||||
different.ID = ""
|
||||
different.CreatedAt = time.Time{}
|
||||
different.UpdatedAt = time.Time{}
|
||||
different.Resolution = "2160p"
|
||||
model.RefreshSubscriptionIdentity(&different)
|
||||
if err := db.Select("*").Omit("DeletedAt").Create(&different).Error; err != nil {
|
||||
t.Fatalf("different rule should be allowed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsurePerformanceIndexesCreatesHotPathIndexes(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
|
||||
@@ -14,12 +14,6 @@ func AutoMigrate(db *gorm.DB) error {
|
||||
if err := ensurePostgresColumnCompatibility(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := enforceTelegramBindingOneToOne(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureSubscriptionIdentityUniqueness(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensurePerformanceIndexes(db); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -44,7 +38,6 @@ func ensurePostgresColumnCompatibility(db *gorm.DB) error {
|
||||
`ALTER TABLE playback_histories ALTER COLUMN media_id TYPE varchar(128)`,
|
||||
`ALTER TABLE favorites ALTER COLUMN media_id TYPE varchar(128)`,
|
||||
`ALTER TABLE playlist_items ALTER COLUMN media_id TYPE varchar(128)`,
|
||||
`ALTER TABLE strm_records ALTER COLUMN media_id TYPE varchar(128)`,
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if err := db.Exec(stmt).Error; err != nil {
|
||||
@@ -84,39 +77,3 @@ func ensurePerformanceIndexes(db *gorm.DB) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func enforceTelegramBindingOneToOne(db *gorm.DB) error {
|
||||
if !db.Migrator().HasTable(&model.TelegramBinding{}) {
|
||||
return nil
|
||||
}
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Exec(`
|
||||
DELETE FROM telegram_bindings
|
||||
WHERE deleted_at IS NULL
|
||||
AND user_id IN (
|
||||
SELECT user_id
|
||||
FROM telegram_bindings
|
||||
WHERE deleted_at IS NULL
|
||||
GROUP BY user_id
|
||||
HAVING COUNT(*) > 1
|
||||
)
|
||||
AND id NOT IN (
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT id,
|
||||
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC, created_at DESC, id DESC) AS rn
|
||||
FROM telegram_bindings
|
||||
WHERE deleted_at IS NULL
|
||||
) AS ranked_bindings
|
||||
WHERE rn = 1
|
||||
)
|
||||
`).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Exec(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_telegram_bindings_user_id_active
|
||||
ON telegram_bindings(user_id)
|
||||
WHERE deleted_at IS NULL
|
||||
`).Error
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
)
|
||||
|
||||
const duplicateSubscriptionMigrationReason = "迁移合并重复订阅规则"
|
||||
|
||||
func ensureSubscriptionIdentityUniqueness(db *gorm.DB) error {
|
||||
if db == nil || !db.Migrator().HasTable(&model.Subscription{}) {
|
||||
return nil
|
||||
}
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
var rows []model.Subscription
|
||||
if err := tx.Unscoped().Order("created_at asc, id asc").Find(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
seen := make(map[string]string)
|
||||
for i := range rows {
|
||||
row := &rows[i]
|
||||
key := model.RefreshSubscriptionIdentity(row)
|
||||
updates := map[string]any{"identity_key": key}
|
||||
if !row.DeletedAt.Valid && row.ArchivedAt == nil {
|
||||
activeKey := row.UserID + "\x00" + key
|
||||
if _, duplicate := seen[activeKey]; duplicate {
|
||||
archivedAt := row.UpdatedAt
|
||||
if archivedAt.IsZero() {
|
||||
archivedAt = row.CreatedAt
|
||||
}
|
||||
if archivedAt.IsZero() {
|
||||
archivedAt = time.Now()
|
||||
}
|
||||
updates["enabled"] = false
|
||||
updates["archived_at"] = &archivedAt
|
||||
updates["archive_reason"] = duplicateSubscriptionMigrationReason
|
||||
} else {
|
||||
seen[activeKey] = row.ID
|
||||
}
|
||||
}
|
||||
if err := tx.Unscoped().Model(&model.Subscription{}).Where("id = ?", row.ID).Updates(updates).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Exec(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_subscriptions_user_identity_active
|
||||
ON subscriptions(user_id, identity_key)
|
||||
WHERE deleted_at IS NULL AND archived_at IS NULL AND identity_key <> ''
|
||||
`).Error
|
||||
})
|
||||
}
|
||||
@@ -68,8 +68,6 @@ func targetLooksLikeBootstrapOnly(target *gorm.DB) (bool, error) {
|
||||
&model.Favorite{},
|
||||
&model.Playlist{},
|
||||
&model.PlaylistItem{},
|
||||
&model.DownloadTask{},
|
||||
&model.Subscription{},
|
||||
} {
|
||||
if !target.Migrator().HasTable(m) {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user