| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- 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,
- }
- }
|