Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// channelMonitorRequestTemplateRepository 实现 service.ChannelMonitorRequestTemplateRepository。
|
||||
// 与 channelMonitorRepository 分开一个文件,职责清晰。
|
||||
type channelMonitorRequestTemplateRepository struct {
|
||||
client *dbent.Client
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewChannelMonitorRequestTemplateRepository 创建模板仓储实例。
|
||||
func NewChannelMonitorRequestTemplateRepository(client *dbent.Client, db *sql.DB) service.ChannelMonitorRequestTemplateRepository {
|
||||
return &channelMonitorRequestTemplateRepository{client: client, db: db}
|
||||
}
|
||||
|
||||
// ---------- CRUD ----------
|
||||
|
||||
func (r *channelMonitorRequestTemplateRepository) Create(ctx context.Context, t *service.ChannelMonitorRequestTemplate) error {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
builder := client.ChannelMonitorRequestTemplate.Create().
|
||||
SetName(t.Name).
|
||||
SetProvider(channelmonitorrequesttemplate.Provider(t.Provider)).
|
||||
SetAPIMode(defaultAPIModeRepo(t.APIMode)).
|
||||
SetDescription(t.Description).
|
||||
SetExtraHeaders(emptyHeadersIfNilRepo(t.ExtraHeaders)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(t.BodyOverrideMode))
|
||||
if t.BodyOverride != nil {
|
||||
builder = builder.SetBodyOverride(t.BodyOverride)
|
||||
}
|
||||
|
||||
created, err := builder.Save(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
|
||||
}
|
||||
t.ID = created.ID
|
||||
t.CreatedAt = created.CreatedAt
|
||||
t.UpdatedAt = created.UpdatedAt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRequestTemplateRepository) GetByID(ctx context.Context, id int64) (*service.ChannelMonitorRequestTemplate, error) {
|
||||
row, err := r.client.ChannelMonitorRequestTemplate.Query().
|
||||
Where(channelmonitorrequesttemplate.IDEQ(id)).
|
||||
Only(ctx)
|
||||
if err != nil {
|
||||
return nil, translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
|
||||
}
|
||||
return entToServiceTemplate(row), nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRequestTemplateRepository) Update(ctx context.Context, t *service.ChannelMonitorRequestTemplate) error {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
updater := client.ChannelMonitorRequestTemplate.UpdateOneID(t.ID).
|
||||
SetName(t.Name).
|
||||
SetAPIMode(defaultAPIModeRepo(t.APIMode)).
|
||||
SetDescription(t.Description).
|
||||
SetExtraHeaders(emptyHeadersIfNilRepo(t.ExtraHeaders)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(t.BodyOverrideMode))
|
||||
if t.BodyOverride != nil {
|
||||
updater = updater.SetBodyOverride(t.BodyOverride)
|
||||
} else {
|
||||
updater = updater.ClearBodyOverride()
|
||||
}
|
||||
updated, err := updater.Save(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
|
||||
}
|
||||
t.UpdatedAt = updated.UpdatedAt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRequestTemplateRepository) Delete(ctx context.Context, id int64) error {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
if err := client.ChannelMonitorRequestTemplate.DeleteOneID(id).Exec(ctx); err != nil {
|
||||
return translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRequestTemplateRepository) List(ctx context.Context, params service.ChannelMonitorRequestTemplateListParams) ([]*service.ChannelMonitorRequestTemplate, error) {
|
||||
q := r.client.ChannelMonitorRequestTemplate.Query()
|
||||
if params.Provider != "" {
|
||||
q = q.Where(channelmonitorrequesttemplate.ProviderEQ(channelmonitorrequesttemplate.Provider(params.Provider)))
|
||||
}
|
||||
if params.APIMode != "" {
|
||||
q = q.Where(channelmonitorrequesttemplate.APIModeEQ(defaultAPIModeRepo(params.APIMode)))
|
||||
}
|
||||
rows, err := q.
|
||||
Order(dbent.Asc(channelmonitorrequesttemplate.FieldProvider), dbent.Asc(channelmonitorrequesttemplate.FieldAPIMode), dbent.Asc(channelmonitorrequesttemplate.FieldName)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list monitor templates: %w", err)
|
||||
}
|
||||
out := make([]*service.ChannelMonitorRequestTemplate, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, entToServiceTemplate(row))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ApplyToMonitors 把模板当前配置覆盖到 monitorIDs 列表里的关联监控。
|
||||
// WHERE 双重过滤:template_id = id AND id IN (monitorIDs),防止用户传了未关联本模板的 id
|
||||
// 就被覆盖。模板字段通过 ent UpdateMany 更新以保留 hooks;extra_headers 在同一事务中
|
||||
// 单独合并,以保留仅用于幂等恢复、绝不会发往上游的内部 operation ID。
|
||||
func (r *channelMonitorRequestTemplateRepository) ApplyToMonitors(ctx context.Context, id int64, monitorIDs []int64) (int64, error) {
|
||||
if len(monitorIDs) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if tx := dbent.TxFromContext(ctx); tx != nil {
|
||||
return r.applyToMonitorsWithClient(ctx, tx.Client(), id, monitorIDs)
|
||||
}
|
||||
|
||||
tx, err := r.client.Tx(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("begin apply template transaction: %w", err)
|
||||
}
|
||||
txCtx := dbent.NewTxContext(ctx, tx)
|
||||
affected, err := r.applyToMonitorsWithClient(txCtx, tx.Client(), id, monitorIDs)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return 0, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return 0, fmt.Errorf("commit apply template transaction: %w", err)
|
||||
}
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRequestTemplateRepository) applyToMonitorsWithClient(
|
||||
ctx context.Context,
|
||||
client *dbent.Client,
|
||||
id int64,
|
||||
monitorIDs []int64,
|
||||
) (int64, error) {
|
||||
tpl, err := client.ChannelMonitorRequestTemplate.Query().
|
||||
Where(channelmonitorrequesttemplate.IDEQ(id)).
|
||||
Only(ctx)
|
||||
if err != nil {
|
||||
return 0, translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
|
||||
}
|
||||
|
||||
updater := client.ChannelMonitor.Update().
|
||||
Where(
|
||||
channelmonitor.TemplateIDEQ(id),
|
||||
channelmonitor.IDIn(monitorIDs...),
|
||||
channelmonitor.ProviderEQ(channelmonitor.Provider(tpl.Provider)),
|
||||
channelmonitor.APIModeEQ(defaultAPIModeRepo(tpl.APIMode)),
|
||||
).
|
||||
SetAPIMode(defaultAPIModeRepo(tpl.APIMode)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(tpl.BodyOverrideMode))
|
||||
if tpl.BodyOverride != nil {
|
||||
updater = updater.SetBodyOverride(tpl.BodyOverride)
|
||||
} else {
|
||||
updater = updater.ClearBodyOverride()
|
||||
}
|
||||
|
||||
affected, err := updater.Save(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("apply template to monitors: %w", err)
|
||||
}
|
||||
if affected == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
templateHeaders := channelMonitorHeadersForPersistence(&service.ChannelMonitor{
|
||||
ExtraHeaders: tpl.ExtraHeaders,
|
||||
})
|
||||
templateHeadersJSON, err := json.Marshal(templateHeaders)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("marshal template headers: %w", err)
|
||||
}
|
||||
result, err := client.ExecContext(ctx, `
|
||||
UPDATE channel_monitors
|
||||
SET extra_headers = $1::jsonb || CASE
|
||||
WHEN COALESCE(extra_headers, '{}'::jsonb) ? ($2::text)
|
||||
THEN jsonb_build_object($2::text, COALESCE(extra_headers, '{}'::jsonb) -> ($2::text))
|
||||
ELSE '{}'::jsonb
|
||||
END
|
||||
WHERE template_id = $3
|
||||
AND id = ANY($4)
|
||||
AND provider = $5
|
||||
AND api_mode = $6
|
||||
`, string(templateHeadersJSON), service.ChannelMonitorDuplicateOperationIDMetadataKey,
|
||||
id, pq.Array(monitorIDs), string(tpl.Provider), defaultAPIModeRepo(tpl.APIMode))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("apply template headers to monitors: %w", err)
|
||||
}
|
||||
headersAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("count applied template headers: %w", err)
|
||||
}
|
||||
if headersAffected != int64(affected) {
|
||||
return 0, fmt.Errorf("apply template headers: affected %d rows, expected %d", headersAffected, affected)
|
||||
}
|
||||
return headersAffected, nil
|
||||
}
|
||||
|
||||
// CountAssociatedMonitors 统计关联监控数(UI 展示「N 个配置」用)。
|
||||
func (r *channelMonitorRequestTemplateRepository) CountAssociatedMonitors(ctx context.Context, id int64) (int64, error) {
|
||||
count, err := r.client.ChannelMonitor.Query().
|
||||
Where(channelmonitor.TemplateIDEQ(id)).
|
||||
Count(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("count monitors for template %d: %w", id, err)
|
||||
}
|
||||
return int64(count), nil
|
||||
}
|
||||
|
||||
// ListAssociatedMonitors 列出模板关联的所有监控简略字段。
|
||||
// ORDER BY name 稳定输出方便前端展示。
|
||||
func (r *channelMonitorRequestTemplateRepository) ListAssociatedMonitors(ctx context.Context, id int64) ([]*service.AssociatedMonitorBrief, error) {
|
||||
rows, err := r.client.ChannelMonitor.Query().
|
||||
Where(channelmonitor.TemplateIDEQ(id)).
|
||||
Order(dbent.Asc(channelmonitor.FieldName)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list associated monitors for template %d: %w", id, err)
|
||||
}
|
||||
out := make([]*service.AssociatedMonitorBrief, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, &service.AssociatedMonitorBrief{
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Provider: string(row.Provider),
|
||||
APIMode: defaultAPIModeRepo(row.APIMode),
|
||||
Enabled: row.Enabled,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
func entToServiceTemplate(row *dbent.ChannelMonitorRequestTemplate) *service.ChannelMonitorRequestTemplate {
|
||||
if row == nil {
|
||||
return nil
|
||||
}
|
||||
headers := row.ExtraHeaders
|
||||
if headers == nil {
|
||||
headers = map[string]string{}
|
||||
}
|
||||
return &service.ChannelMonitorRequestTemplate{
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Provider: string(row.Provider),
|
||||
APIMode: defaultAPIModeRepo(row.APIMode),
|
||||
Description: row.Description,
|
||||
ExtraHeaders: headers,
|
||||
BodyOverrideMode: row.BodyOverrideMode,
|
||||
BodyOverride: row.BodyOverride,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user