mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
943818f7d4
将 OpenFlare 与平台业务的数据访问从 model 与 apps 直连迁入 repository, model 仅保留实体与无 IO 规则;补充 code-check 架构守卫与开发规范。
358 lines
11 KiB
Go
358 lines
11 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
)
|
|
|
|
func wafDB(ctx context.Context) (*gorm.DB, error) {
|
|
conn := db.DB(ctx)
|
|
if conn == nil {
|
|
return nil, errors.New(errDatabaseNotInitialized)
|
|
}
|
|
return conn, nil
|
|
}
|
|
|
|
// ListOpenFlareWAFRuleGroups returns all rule groups.
|
|
func ListOpenFlareWAFRuleGroups(ctx context.Context) ([]*model.OpenFlareWAFRuleGroup, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var groups []*model.OpenFlareWAFRuleGroup
|
|
if err = conn.Order("is_global desc").Order("id asc").Find(&groups).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
// GetOpenFlareWAFRuleGroupByID returns a rule group by id.
|
|
func GetOpenFlareWAFRuleGroupByID(ctx context.Context, id uint) (*model.OpenFlareWAFRuleGroup, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var group model.OpenFlareWAFRuleGroup
|
|
if err = conn.First(&group, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &group, nil
|
|
}
|
|
|
|
// GetGlobalOpenFlareWAFRuleGroup returns the global rule group if present.
|
|
func GetGlobalOpenFlareWAFRuleGroup(ctx context.Context) (*model.OpenFlareWAFRuleGroup, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var group model.OpenFlareWAFRuleGroup
|
|
if err = conn.Where("is_global = ?", true).Order("id asc").First(&group).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &group, nil
|
|
}
|
|
|
|
// CreateOpenFlareWAFRuleGroup inserts a rule group.
|
|
func CreateOpenFlareWAFRuleGroup(ctx context.Context, group *model.OpenFlareWAFRuleGroup) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Create(group).Error
|
|
}
|
|
|
|
// UpdateOpenFlareWAFRuleGroup updates mutable rule group fields.
|
|
func UpdateOpenFlareWAFRuleGroup(ctx context.Context, group *model.OpenFlareWAFRuleGroup) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Model(&model.OpenFlareWAFRuleGroup{}).Where("id = ?", group.ID).Updates(map[string]any{
|
|
"name": group.Name,
|
|
colEnabled: group.Enabled,
|
|
"is_global": group.IsGlobal,
|
|
}).Error
|
|
}
|
|
|
|
// UpdateOpenFlareWAFRuleGraph atomically replaces a graph when revision is current.
|
|
func UpdateOpenFlareWAFRuleGraph(ctx context.Context, id uint, revision uint64, graph string) (uint64, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
result := conn.Model(&model.OpenFlareWAFRuleGroup{}).
|
|
Where("id = ? AND revision = ?", id, revision).
|
|
Updates(map[string]any{"graph": graph, "revision": gorm.Expr("revision + 1")})
|
|
if result.Error != nil {
|
|
return 0, result.Error
|
|
}
|
|
if result.RowsAffected != 1 {
|
|
return 0, model.ErrWAFRuleRevisionConflict
|
|
}
|
|
return revision + 1, nil
|
|
}
|
|
|
|
// DeleteOpenFlareWAFRuleGroup removes a rule group.
|
|
func DeleteOpenFlareWAFRuleGroup(ctx context.Context, id uint) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Delete(&model.OpenFlareWAFRuleGroup{}, id).Error
|
|
}
|
|
|
|
// ListOpenFlareWAFIPGroups returns all IP groups.
|
|
func ListOpenFlareWAFIPGroups(ctx context.Context) ([]*model.OpenFlareWAFIPGroup, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var groups []*model.OpenFlareWAFIPGroup
|
|
if err = conn.Order("type asc").Order("id asc").Find(&groups).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
// ListOpenFlareWAFIPGroupsByIDs returns IP groups for the given ids.
|
|
func ListOpenFlareWAFIPGroupsByIDs(ctx context.Context, ids []uint) ([]*model.OpenFlareWAFIPGroup, error) {
|
|
if len(ids) == 0 {
|
|
return []*model.OpenFlareWAFIPGroup{}, nil
|
|
}
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var groups []*model.OpenFlareWAFIPGroup
|
|
if err = conn.Where("id IN ?", ids).Find(&groups).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
// GetOpenFlareWAFIPGroupByID returns an IP group by id.
|
|
func GetOpenFlareWAFIPGroupByID(ctx context.Context, id uint) (*model.OpenFlareWAFIPGroup, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var group model.OpenFlareWAFIPGroup
|
|
if err = conn.First(&group, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &group, nil
|
|
}
|
|
|
|
// CreateOpenFlareWAFIPGroup inserts an IP group.
|
|
func CreateOpenFlareWAFIPGroup(ctx context.Context, group *model.OpenFlareWAFIPGroup) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Create(group).Error
|
|
}
|
|
|
|
// UpdateOpenFlareWAFIPGroup updates mutable IP group fields.
|
|
func UpdateOpenFlareWAFIPGroup(ctx context.Context, group *model.OpenFlareWAFIPGroup) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Model(&model.OpenFlareWAFIPGroup{}).Where("id = ?", group.ID).Updates(map[string]any{
|
|
"name": group.Name,
|
|
"type": group.Type,
|
|
colEnabled: group.Enabled,
|
|
"ip_list": group.IPList,
|
|
"auto_config": group.AutoConfig,
|
|
"ext_ips": group.ExtIPs,
|
|
"subscription_url": group.SubscriptionURL,
|
|
"subscription_format": group.SubscriptionFormat,
|
|
"subscription_mapping_rule": group.SubscriptionMappingRule,
|
|
"sync_interval_minutes": group.SyncIntervalMinutes,
|
|
"next_sync_at": group.NextSyncAt,
|
|
"last_sync_status": group.LastSyncStatus,
|
|
"last_sync_message": group.LastSyncMessage,
|
|
}).Error
|
|
}
|
|
|
|
// ListDueOpenFlareWAFIPGroups returns enabled automatic/subscription groups due for sync.
|
|
func ListDueOpenFlareWAFIPGroups(ctx context.Context, now time.Time) ([]*model.OpenFlareWAFIPGroup, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var groups []*model.OpenFlareWAFIPGroup
|
|
err = conn.Where(
|
|
"enabled = ? AND (type = ? OR (type = ? AND subscription_url <> '')) AND (next_sync_at IS NULL OR next_sync_at <= ?)",
|
|
true, "automatic", "subscription", now,
|
|
).Order("id asc").Find(&groups).Error
|
|
return groups, err
|
|
}
|
|
|
|
// UpdateOpenFlareWAFIPGroupSyncResult persists IP group sync outcome fields.
|
|
func UpdateOpenFlareWAFIPGroupSyncResult(ctx context.Context, group *model.OpenFlareWAFIPGroup) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Model(&model.OpenFlareWAFIPGroup{}).Where("id = ?", group.ID).Updates(map[string]any{
|
|
"ip_list": group.IPList,
|
|
"ext_ips": group.ExtIPs,
|
|
"last_synced_at": group.LastSyncedAt,
|
|
"next_sync_at": group.NextSyncAt,
|
|
"last_sync_status": group.LastSyncStatus,
|
|
"last_sync_message": group.LastSyncMessage,
|
|
"subscription_format": group.SubscriptionFormat,
|
|
}).Error
|
|
}
|
|
|
|
// DeleteOpenFlareWAFIPGroup removes an IP group.
|
|
func DeleteOpenFlareWAFIPGroup(ctx context.Context, id uint) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Delete(&model.OpenFlareWAFIPGroup{}, id).Error
|
|
}
|
|
|
|
// ListOpenFlareWAFRuleGroupBindings returns all bindings.
|
|
func ListOpenFlareWAFRuleGroupBindings(ctx context.Context) ([]model.OpenFlareWAFRuleGroupBinding, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var bindings []model.OpenFlareWAFRuleGroupBinding
|
|
if err = conn.Order("sequence asc").Order("id asc").Find(&bindings).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return bindings, nil
|
|
}
|
|
|
|
// ListOpenFlareWAFRuleGroupBindingsByRouteID returns bindings for a proxy route.
|
|
func ListOpenFlareWAFRuleGroupBindingsByRouteID(ctx context.Context, routeID uint) ([]model.OpenFlareWAFRuleGroupBinding, error) {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var bindings []model.OpenFlareWAFRuleGroupBinding
|
|
if err = conn.Where("proxy_route_id = ?", routeID).Order("sequence asc").Order("id asc").Find(&bindings).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return bindings, nil
|
|
}
|
|
|
|
func syncWAFBindingIDSequence(tx *gorm.DB) error {
|
|
if tx == nil || tx.Dialector.Name() != "postgres" { //nolint:staticcheck // QF1008: keep explicit Dialector field access
|
|
return nil
|
|
}
|
|
return tx.Exec(`
|
|
SELECT setval(
|
|
pg_get_serial_sequence('of_waf_rule_group_bindings', 'id'),
|
|
GREATEST(COALESCE((SELECT MAX(id) FROM of_waf_rule_group_bindings), 0), 1),
|
|
COALESCE((SELECT MAX(id) FROM of_waf_rule_group_bindings), 0) > 0
|
|
)
|
|
`).Error
|
|
}
|
|
|
|
func insertOpenFlareWAFRuleGroupBindings(tx *gorm.DB, bindings []model.OpenFlareWAFRuleGroupBinding) error {
|
|
if len(bindings) == 0 {
|
|
return nil
|
|
}
|
|
if err := syncWAFBindingIDSequence(tx); err != nil {
|
|
return err
|
|
}
|
|
return tx.Create(&bindings).Error
|
|
}
|
|
|
|
// ReplaceOpenFlareWAFRuleGroupBindings replaces bindings for a rule group.
|
|
func ReplaceOpenFlareWAFRuleGroupBindings(ctx context.Context, groupID uint, routeIDs []uint) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Transaction(func(tx *gorm.DB) error {
|
|
if err = tx.Where("rule_group_id = ?", groupID).Delete(&model.OpenFlareWAFRuleGroupBinding{}).Error; err != nil {
|
|
return err
|
|
}
|
|
bindings := make([]model.OpenFlareWAFRuleGroupBinding, 0, len(routeIDs))
|
|
for index, routeID := range routeIDs {
|
|
bindings = append(bindings, model.OpenFlareWAFRuleGroupBinding{
|
|
RuleGroupID: groupID,
|
|
ProxyRouteID: routeID,
|
|
Sequence: index,
|
|
})
|
|
}
|
|
return insertOpenFlareWAFRuleGroupBindings(tx, bindings)
|
|
})
|
|
}
|
|
|
|
// ReplaceOpenFlareWAFSiteRuleGroupBindings replaces bindings for a proxy route.
|
|
func ReplaceOpenFlareWAFSiteRuleGroupBindings(ctx context.Context, routeID uint, groupIDs []uint) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Transaction(func(tx *gorm.DB) error {
|
|
if err = tx.Where("proxy_route_id = ?", routeID).Delete(&model.OpenFlareWAFRuleGroupBinding{}).Error; err != nil {
|
|
return err
|
|
}
|
|
bindings := make([]model.OpenFlareWAFRuleGroupBinding, 0, len(groupIDs))
|
|
for index, groupID := range groupIDs {
|
|
bindings = append(bindings, model.OpenFlareWAFRuleGroupBinding{
|
|
RuleGroupID: groupID,
|
|
ProxyRouteID: routeID,
|
|
Sequence: index,
|
|
})
|
|
}
|
|
return insertOpenFlareWAFRuleGroupBindings(tx, bindings)
|
|
})
|
|
}
|
|
|
|
// DeleteOpenFlareWAFRuleGroupBindingsByGroupID removes bindings for a rule group.
|
|
func DeleteOpenFlareWAFRuleGroupBindingsByGroupID(ctx context.Context, groupID uint) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Where("rule_group_id = ?", groupID).Delete(&model.OpenFlareWAFRuleGroupBinding{}).Error
|
|
}
|
|
|
|
// DeleteOpenFlareWAFRuleGroupWithBindings removes a rule group and its bindings.
|
|
func DeleteOpenFlareWAFRuleGroupWithBindings(ctx context.Context, groupID uint) error {
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return conn.Transaction(func(tx *gorm.DB) error {
|
|
if err = tx.Where("rule_group_id = ?", groupID).Delete(&model.OpenFlareWAFRuleGroupBinding{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Delete(&model.OpenFlareWAFRuleGroup{}, groupID).Error
|
|
})
|
|
}
|
|
|
|
// GetOpenFlareProxyRouteByID returns a proxy route by id when the table exists.
|
|
func GetOpenFlareProxyRouteByID(ctx context.Context, id uint) (*model.OriginProxyRoute, error) {
|
|
if !HasProxyRoutesTable(ctx) {
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
conn, err := wafDB(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var route model.OriginProxyRoute
|
|
if err = conn.First(&route, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &route, nil
|
|
}
|