mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 07:06:36 +08:00
fix(cloudflare): clean up pointing member when zone domain is deleted
This commit is contained in:
@@ -423,6 +423,13 @@ func listMemberItems(ctx context.Context, groupID uint) ([]MemberItem, error) {
|
||||
for i := range members {
|
||||
domain, domainErr := repository.GetZoneDomainByID(ctx, members[i].ZoneDomainID)
|
||||
if domainErr != nil {
|
||||
if errors.Is(domainErr, gorm.ErrRecordNotFound) {
|
||||
logger.WarnF(ctx, "[Cloudflare] cleaning up orphaned pointing member: member_id=%d zone_domain_id=%d", members[i].ID, members[i].ZoneDomainID)
|
||||
if delErr := repository.DeleteCFPointingMember(ctx, &members[i]); delErr != nil {
|
||||
logger.ErrorF(ctx, "[Cloudflare] delete orphaned member failed: member_id=%d error=%v", members[i].ID, delErr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, domainErr
|
||||
}
|
||||
items = append(items, *memberItem(&members[i], domain))
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -116,6 +117,9 @@ func markMemberSynced(ctx context.Context, memberID uint, zoneID, recordID, ip s
|
||||
func DeleteManagedRecord(ctx context.Context, memberID uint) error {
|
||||
state, err := repository.GetCFPointingMemberContext(ctx, memberID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
connection, err := repository.GetCFConnection(ctx)
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/internal/shared/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -41,3 +43,35 @@ func TestConnectionHandlersNeverReturnAPIToken(t *testing.T) {
|
||||
t.Fatalf("GET /connection leaked token: %s", get.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGroupWithOrphanedMemberHealsAndSucceeds(t *testing.T) {
|
||||
ctx, memberID := setupCloudflareLogicDB(t)
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
router.Use(response.ErrorHandlerMiddleware())
|
||||
router.GET("/groups/:id", GetGroupHandler)
|
||||
|
||||
member, err := repository.GetCFPointingMemberByID(ctx, memberID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCFPointingMemberByID() error = %v", err)
|
||||
}
|
||||
|
||||
// Simulate orphaned member by deleting the ZoneDomain directly
|
||||
if err := db.DB(ctx).Exec("DELETE FROM of_zone_domains WHERE id = ?", member.ZoneDomainID).Error; err != nil {
|
||||
t.Fatalf("DELETE FROM of_zone_domains error = %v", err)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/groups/1", nil)
|
||||
router.ServeHTTP(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("GET /groups/1 status = %d, body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
|
||||
// Verify the orphaned member has been removed
|
||||
_, err = repository.GetCFPointingMemberByID(ctx, memberID)
|
||||
if err == nil {
|
||||
t.Errorf("GetCFPointingMemberByID() should return not found after healing")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/infra/task"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -317,10 +318,19 @@ func executeBatchSync(
|
||||
return &task.TaskResult{Message: message}, nil
|
||||
}
|
||||
|
||||
syncedCount := 0
|
||||
for index, member := range members {
|
||||
domainName := fmt.Sprintf("zone_domain_id=%d", member.ZoneDomainID)
|
||||
if domain, domainErr := repository.GetZoneDomainByID(ctx, member.ZoneDomainID); domainErr == nil {
|
||||
domainName = domain.Domain
|
||||
} else if errors.Is(domainErr, gorm.ErrRecordNotFound) {
|
||||
task.AppendLog(ctx, "[%d/%d] 域名记录已不存在,清理孤立成员: member_id=%d zone_domain_id=%d",
|
||||
index+1, len(members), member.ID, member.ZoneDomainID)
|
||||
if delErr := repository.DeleteCFPointingMember(ctx, &member); delErr != nil {
|
||||
task.AppendLog(ctx, "[%d/%d] 清理孤立成员失败: member_id=%d error=%v",
|
||||
index+1, len(members), member.ID, delErr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
task.AppendLog(ctx, "[%d/%d] 同步域名 %s (member_id=%d proxied=%v)",
|
||||
index+1, len(members), domainName, member.ID, member.Proxied)
|
||||
@@ -329,13 +339,14 @@ func executeBatchSync(
|
||||
index+1, len(members), domainName, member.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
syncedCount++
|
||||
task.AppendLog(ctx, "[%d/%d] 成功: domain=%s", index+1, len(members), domainName)
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Cloudflare %s同步完成: %s 共 %d 个域名", scope, scopeName, len(members))
|
||||
message := fmt.Sprintf("Cloudflare %s同步完成: %s 共 %d 个域名", scope, scopeName, syncedCount)
|
||||
if activeNode != "" {
|
||||
message = fmt.Sprintf("Cloudflare %s同步完成: %s → %s,共 %d 个域名",
|
||||
scope, scopeName, activeNode, len(members))
|
||||
scope, scopeName, activeNode, syncedCount)
|
||||
}
|
||||
task.AppendLog(ctx, "%s", message)
|
||||
return &task.TaskResult{Message: message}, nil
|
||||
|
||||
Reference in New Issue
Block a user