channel_monitor_template_repo.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. package repository
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/json"
  6. "fmt"
  7. dbent "github.com/Wei-Shaw/sub2api/ent"
  8. "github.com/Wei-Shaw/sub2api/ent/channelmonitor"
  9. "github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
  10. "github.com/Wei-Shaw/sub2api/internal/service"
  11. "github.com/lib/pq"
  12. )
  13. // channelMonitorRequestTemplateRepository 实现 service.ChannelMonitorRequestTemplateRepository。
  14. // 与 channelMonitorRepository 分开一个文件,职责清晰。
  15. type channelMonitorRequestTemplateRepository struct {
  16. client *dbent.Client
  17. db *sql.DB
  18. }
  19. // NewChannelMonitorRequestTemplateRepository 创建模板仓储实例。
  20. func NewChannelMonitorRequestTemplateRepository(client *dbent.Client, db *sql.DB) service.ChannelMonitorRequestTemplateRepository {
  21. return &channelMonitorRequestTemplateRepository{client: client, db: db}
  22. }
  23. // ---------- CRUD ----------
  24. func (r *channelMonitorRequestTemplateRepository) Create(ctx context.Context, t *service.ChannelMonitorRequestTemplate) error {
  25. client := clientFromContext(ctx, r.client)
  26. builder := client.ChannelMonitorRequestTemplate.Create().
  27. SetName(t.Name).
  28. SetProvider(channelmonitorrequesttemplate.Provider(t.Provider)).
  29. SetAPIMode(defaultAPIModeRepo(t.APIMode)).
  30. SetDescription(t.Description).
  31. SetExtraHeaders(emptyHeadersIfNilRepo(t.ExtraHeaders)).
  32. SetBodyOverrideMode(defaultBodyModeRepo(t.BodyOverrideMode))
  33. if t.BodyOverride != nil {
  34. builder = builder.SetBodyOverride(t.BodyOverride)
  35. }
  36. created, err := builder.Save(ctx)
  37. if err != nil {
  38. return translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
  39. }
  40. t.ID = created.ID
  41. t.CreatedAt = created.CreatedAt
  42. t.UpdatedAt = created.UpdatedAt
  43. return nil
  44. }
  45. func (r *channelMonitorRequestTemplateRepository) GetByID(ctx context.Context, id int64) (*service.ChannelMonitorRequestTemplate, error) {
  46. row, err := r.client.ChannelMonitorRequestTemplate.Query().
  47. Where(channelmonitorrequesttemplate.IDEQ(id)).
  48. Only(ctx)
  49. if err != nil {
  50. return nil, translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
  51. }
  52. return entToServiceTemplate(row), nil
  53. }
  54. func (r *channelMonitorRequestTemplateRepository) Update(ctx context.Context, t *service.ChannelMonitorRequestTemplate) error {
  55. client := clientFromContext(ctx, r.client)
  56. updater := client.ChannelMonitorRequestTemplate.UpdateOneID(t.ID).
  57. SetName(t.Name).
  58. SetAPIMode(defaultAPIModeRepo(t.APIMode)).
  59. SetDescription(t.Description).
  60. SetExtraHeaders(emptyHeadersIfNilRepo(t.ExtraHeaders)).
  61. SetBodyOverrideMode(defaultBodyModeRepo(t.BodyOverrideMode))
  62. if t.BodyOverride != nil {
  63. updater = updater.SetBodyOverride(t.BodyOverride)
  64. } else {
  65. updater = updater.ClearBodyOverride()
  66. }
  67. updated, err := updater.Save(ctx)
  68. if err != nil {
  69. return translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
  70. }
  71. t.UpdatedAt = updated.UpdatedAt
  72. return nil
  73. }
  74. func (r *channelMonitorRequestTemplateRepository) Delete(ctx context.Context, id int64) error {
  75. client := clientFromContext(ctx, r.client)
  76. if err := client.ChannelMonitorRequestTemplate.DeleteOneID(id).Exec(ctx); err != nil {
  77. return translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
  78. }
  79. return nil
  80. }
  81. func (r *channelMonitorRequestTemplateRepository) List(ctx context.Context, params service.ChannelMonitorRequestTemplateListParams) ([]*service.ChannelMonitorRequestTemplate, error) {
  82. q := r.client.ChannelMonitorRequestTemplate.Query()
  83. if params.Provider != "" {
  84. q = q.Where(channelmonitorrequesttemplate.ProviderEQ(channelmonitorrequesttemplate.Provider(params.Provider)))
  85. }
  86. if params.APIMode != "" {
  87. q = q.Where(channelmonitorrequesttemplate.APIModeEQ(defaultAPIModeRepo(params.APIMode)))
  88. }
  89. rows, err := q.
  90. Order(dbent.Asc(channelmonitorrequesttemplate.FieldProvider), dbent.Asc(channelmonitorrequesttemplate.FieldAPIMode), dbent.Asc(channelmonitorrequesttemplate.FieldName)).
  91. All(ctx)
  92. if err != nil {
  93. return nil, fmt.Errorf("list monitor templates: %w", err)
  94. }
  95. out := make([]*service.ChannelMonitorRequestTemplate, 0, len(rows))
  96. for _, row := range rows {
  97. out = append(out, entToServiceTemplate(row))
  98. }
  99. return out, nil
  100. }
  101. // ApplyToMonitors 把模板当前配置覆盖到 monitorIDs 列表里的关联监控。
  102. // WHERE 双重过滤:template_id = id AND id IN (monitorIDs),防止用户传了未关联本模板的 id
  103. // 就被覆盖。模板字段通过 ent UpdateMany 更新以保留 hooks;extra_headers 在同一事务中
  104. // 单独合并,以保留仅用于幂等恢复、绝不会发往上游的内部 operation ID。
  105. func (r *channelMonitorRequestTemplateRepository) ApplyToMonitors(ctx context.Context, id int64, monitorIDs []int64) (int64, error) {
  106. if len(monitorIDs) == 0 {
  107. return 0, nil
  108. }
  109. if tx := dbent.TxFromContext(ctx); tx != nil {
  110. return r.applyToMonitorsWithClient(ctx, tx.Client(), id, monitorIDs)
  111. }
  112. tx, err := r.client.Tx(ctx)
  113. if err != nil {
  114. return 0, fmt.Errorf("begin apply template transaction: %w", err)
  115. }
  116. txCtx := dbent.NewTxContext(ctx, tx)
  117. affected, err := r.applyToMonitorsWithClient(txCtx, tx.Client(), id, monitorIDs)
  118. if err != nil {
  119. _ = tx.Rollback()
  120. return 0, err
  121. }
  122. if err := tx.Commit(); err != nil {
  123. return 0, fmt.Errorf("commit apply template transaction: %w", err)
  124. }
  125. return affected, nil
  126. }
  127. func (r *channelMonitorRequestTemplateRepository) applyToMonitorsWithClient(
  128. ctx context.Context,
  129. client *dbent.Client,
  130. id int64,
  131. monitorIDs []int64,
  132. ) (int64, error) {
  133. tpl, err := client.ChannelMonitorRequestTemplate.Query().
  134. Where(channelmonitorrequesttemplate.IDEQ(id)).
  135. Only(ctx)
  136. if err != nil {
  137. return 0, translatePersistenceError(err, service.ErrChannelMonitorTemplateNotFound, nil)
  138. }
  139. updater := client.ChannelMonitor.Update().
  140. Where(
  141. channelmonitor.TemplateIDEQ(id),
  142. channelmonitor.IDIn(monitorIDs...),
  143. channelmonitor.ProviderEQ(channelmonitor.Provider(tpl.Provider)),
  144. channelmonitor.APIModeEQ(defaultAPIModeRepo(tpl.APIMode)),
  145. ).
  146. SetAPIMode(defaultAPIModeRepo(tpl.APIMode)).
  147. SetBodyOverrideMode(defaultBodyModeRepo(tpl.BodyOverrideMode))
  148. if tpl.BodyOverride != nil {
  149. updater = updater.SetBodyOverride(tpl.BodyOverride)
  150. } else {
  151. updater = updater.ClearBodyOverride()
  152. }
  153. affected, err := updater.Save(ctx)
  154. if err != nil {
  155. return 0, fmt.Errorf("apply template to monitors: %w", err)
  156. }
  157. if affected == 0 {
  158. return 0, nil
  159. }
  160. templateHeaders := channelMonitorHeadersForPersistence(&service.ChannelMonitor{
  161. ExtraHeaders: tpl.ExtraHeaders,
  162. })
  163. templateHeadersJSON, err := json.Marshal(templateHeaders)
  164. if err != nil {
  165. return 0, fmt.Errorf("marshal template headers: %w", err)
  166. }
  167. result, err := client.ExecContext(ctx, `
  168. UPDATE channel_monitors
  169. SET extra_headers = $1::jsonb || CASE
  170. WHEN COALESCE(extra_headers, '{}'::jsonb) ? ($2::text)
  171. THEN jsonb_build_object($2::text, COALESCE(extra_headers, '{}'::jsonb) -> ($2::text))
  172. ELSE '{}'::jsonb
  173. END
  174. WHERE template_id = $3
  175. AND id = ANY($4)
  176. AND provider = $5
  177. AND api_mode = $6
  178. `, string(templateHeadersJSON), service.ChannelMonitorDuplicateOperationIDMetadataKey,
  179. id, pq.Array(monitorIDs), string(tpl.Provider), defaultAPIModeRepo(tpl.APIMode))
  180. if err != nil {
  181. return 0, fmt.Errorf("apply template headers to monitors: %w", err)
  182. }
  183. headersAffected, err := result.RowsAffected()
  184. if err != nil {
  185. return 0, fmt.Errorf("count applied template headers: %w", err)
  186. }
  187. if headersAffected != int64(affected) {
  188. return 0, fmt.Errorf("apply template headers: affected %d rows, expected %d", headersAffected, affected)
  189. }
  190. return headersAffected, nil
  191. }
  192. // CountAssociatedMonitors 统计关联监控数(UI 展示「N 个配置」用)。
  193. func (r *channelMonitorRequestTemplateRepository) CountAssociatedMonitors(ctx context.Context, id int64) (int64, error) {
  194. count, err := r.client.ChannelMonitor.Query().
  195. Where(channelmonitor.TemplateIDEQ(id)).
  196. Count(ctx)
  197. if err != nil {
  198. return 0, fmt.Errorf("count monitors for template %d: %w", id, err)
  199. }
  200. return int64(count), nil
  201. }
  202. // ListAssociatedMonitors 列出模板关联的所有监控简略字段。
  203. // ORDER BY name 稳定输出方便前端展示。
  204. func (r *channelMonitorRequestTemplateRepository) ListAssociatedMonitors(ctx context.Context, id int64) ([]*service.AssociatedMonitorBrief, error) {
  205. rows, err := r.client.ChannelMonitor.Query().
  206. Where(channelmonitor.TemplateIDEQ(id)).
  207. Order(dbent.Asc(channelmonitor.FieldName)).
  208. All(ctx)
  209. if err != nil {
  210. return nil, fmt.Errorf("list associated monitors for template %d: %w", id, err)
  211. }
  212. out := make([]*service.AssociatedMonitorBrief, 0, len(rows))
  213. for _, row := range rows {
  214. out = append(out, &service.AssociatedMonitorBrief{
  215. ID: row.ID,
  216. Name: row.Name,
  217. Provider: string(row.Provider),
  218. APIMode: defaultAPIModeRepo(row.APIMode),
  219. Enabled: row.Enabled,
  220. })
  221. }
  222. return out, nil
  223. }
  224. // ---------- helpers ----------
  225. func entToServiceTemplate(row *dbent.ChannelMonitorRequestTemplate) *service.ChannelMonitorRequestTemplate {
  226. if row == nil {
  227. return nil
  228. }
  229. headers := row.ExtraHeaders
  230. if headers == nil {
  231. headers = map[string]string{}
  232. }
  233. return &service.ChannelMonitorRequestTemplate{
  234. ID: row.ID,
  235. Name: row.Name,
  236. Provider: string(row.Provider),
  237. APIMode: defaultAPIModeRepo(row.APIMode),
  238. Description: row.Description,
  239. ExtraHeaders: headers,
  240. BodyOverrideMode: row.BodyOverrideMode,
  241. BodyOverride: row.BodyOverride,
  242. CreatedAt: row.CreatedAt,
  243. UpdatedAt: row.UpdatedAt,
  244. }
  245. }