global_model_pricing_repo.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package repository
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "strings"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. )
  9. // globalModelPricingRepository 实现 service.GlobalModelPricingRepository。
  10. // 直接 SQL 访问 global_model_pricing 表(无需 ent codegen)。
  11. type globalModelPricingRepository struct {
  12. sql sqlExecutor
  13. }
  14. func NewGlobalModelPricingRepository(sqlDB *sql.DB) service.GlobalModelPricingRepository {
  15. return &globalModelPricingRepository{sql: sqlDB}
  16. }
  17. const globalModelPricingColumns = `id, model_pattern, billing_mode, input_price, output_price, cache_write_price, cache_write_1h_price, cache_read_price, per_request_price, enabled, created_at, updated_at`
  18. func scanGlobalModelPricingRow(scan func(dest ...any) error) (*service.GlobalModelPrice, error) {
  19. var p service.GlobalModelPrice
  20. var input, output, cw, cw1h, cr, pr sql.NullFloat64
  21. var created, updated sql.NullTime
  22. if err := scan(&p.ID, &p.ModelPattern, &p.BillingMode,
  23. &input, &output, &cw, &cw1h, &cr, &pr,
  24. &p.Enabled, &created, &updated); err != nil {
  25. return nil, err
  26. }
  27. if input.Valid {
  28. p.InputPrice = &input.Float64
  29. }
  30. if output.Valid {
  31. p.OutputPrice = &output.Float64
  32. }
  33. if cw.Valid {
  34. p.CacheWritePrice = &cw.Float64
  35. }
  36. if cw1h.Valid {
  37. p.CacheWrite1hPrice = &cw1h.Float64
  38. }
  39. if cr.Valid {
  40. p.CacheReadPrice = &cr.Float64
  41. }
  42. if pr.Valid {
  43. p.PerRequestPrice = &pr.Float64
  44. }
  45. if created.Valid {
  46. p.CreatedAt = created.Time
  47. }
  48. if updated.Valid {
  49. p.UpdatedAt = updated.Time
  50. }
  51. return &p, nil
  52. }
  53. // ListEnabled 返回全部启用的全局定价(service 层做快照缓存)。
  54. func (r *globalModelPricingRepository) ListEnabled(ctx context.Context) ([]service.GlobalModelPrice, error) {
  55. rows, err := r.sql.QueryContext(ctx,
  56. `SELECT `+globalModelPricingColumns+` FROM global_model_pricing WHERE enabled = TRUE ORDER BY id`)
  57. if err != nil {
  58. return nil, err
  59. }
  60. defer func() { _ = rows.Close() }()
  61. out := make([]service.GlobalModelPrice, 0)
  62. for rows.Next() {
  63. p, err := scanGlobalModelPricingRow(rows.Scan)
  64. if err != nil {
  65. return nil, err
  66. }
  67. out = append(out, *p)
  68. }
  69. return out, rows.Err()
  70. }
  71. // ListAll 返回全部(含禁用,供管理端列表)。
  72. func (r *globalModelPricingRepository) ListAll(ctx context.Context) ([]service.GlobalModelPrice, error) {
  73. rows, err := r.sql.QueryContext(ctx,
  74. `SELECT `+globalModelPricingColumns+` FROM global_model_pricing ORDER BY id`)
  75. if err != nil {
  76. return nil, err
  77. }
  78. defer func() { _ = rows.Close() }()
  79. out := make([]service.GlobalModelPrice, 0)
  80. for rows.Next() {
  81. p, err := scanGlobalModelPricingRow(rows.Scan)
  82. if err != nil {
  83. return nil, err
  84. }
  85. out = append(out, *p)
  86. }
  87. return out, rows.Err()
  88. }
  89. func nullFloat64(v *float64) any {
  90. if v == nil {
  91. return nil
  92. }
  93. return *v
  94. }
  95. func (r *globalModelPricingRepository) Create(ctx context.Context, in service.GlobalModelPriceInput) (*service.GlobalModelPrice, error) {
  96. pattern := strings.ToLower(strings.TrimSpace(in.ModelPattern))
  97. mode := strings.TrimSpace(string(in.BillingMode))
  98. if mode == "" {
  99. mode = string(service.BillingModeToken)
  100. }
  101. rows, err := r.sql.QueryContext(ctx, `
  102. INSERT INTO global_model_pricing
  103. (model_pattern, billing_mode, input_price, output_price, cache_write_price,
  104. cache_write_1h_price, cache_read_price, per_request_price, enabled, created_at, updated_at)
  105. VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,NOW(),NOW())
  106. RETURNING `+globalModelPricingColumns,
  107. pattern, mode,
  108. nullFloat64(in.InputPrice), nullFloat64(in.OutputPrice),
  109. nullFloat64(in.CacheWritePrice), nullFloat64(in.CacheWrite1hPrice),
  110. nullFloat64(in.CacheReadPrice), nullFloat64(in.PerRequestPrice),
  111. in.Enabled)
  112. if err != nil {
  113. if isUniqueConstraintViolation(err) {
  114. return nil, fmt.Errorf("create global model pricing %q: %w", pattern, service.ErrGlobalModelPricingDuplicate)
  115. }
  116. return nil, fmt.Errorf("create global model pricing: %w", err)
  117. }
  118. defer func() { _ = rows.Close() }()
  119. if !rows.Next() {
  120. return nil, fmt.Errorf("create global model pricing: no row returned")
  121. }
  122. p, err := scanGlobalModelPricingRow(rows.Scan)
  123. if err != nil {
  124. return nil, err
  125. }
  126. return p, rows.Err()
  127. }
  128. func (r *globalModelPricingRepository) GetByID(ctx context.Context, id int64) (*service.GlobalModelPrice, error) {
  129. rows, err := r.sql.QueryContext(ctx,
  130. `SELECT `+globalModelPricingColumns+` FROM global_model_pricing WHERE id = $1`, id)
  131. if err != nil {
  132. return nil, err
  133. }
  134. defer func() { _ = rows.Close() }()
  135. if !rows.Next() {
  136. return nil, sql.ErrNoRows
  137. }
  138. p, err := scanGlobalModelPricingRow(rows.Scan)
  139. if err != nil {
  140. return nil, err
  141. }
  142. return p, rows.Err()
  143. }
  144. func (r *globalModelPricingRepository) Update(ctx context.Context, id int64, in service.GlobalModelPriceInput) (*service.GlobalModelPrice, error) {
  145. pattern := strings.ToLower(strings.TrimSpace(in.ModelPattern))
  146. mode := strings.TrimSpace(string(in.BillingMode))
  147. if mode == "" {
  148. mode = string(service.BillingModeToken)
  149. }
  150. rows, err := r.sql.QueryContext(ctx, `
  151. UPDATE global_model_pricing SET
  152. model_pattern = $2, billing_mode = $3,
  153. input_price = $4, output_price = $5,
  154. cache_write_price = $6, cache_write_1h_price = $7, cache_read_price = $8,
  155. per_request_price = $9, enabled = $10, updated_at = NOW()
  156. WHERE id = $1
  157. RETURNING `+globalModelPricingColumns, id, pattern, mode,
  158. nullFloat64(in.InputPrice), nullFloat64(in.OutputPrice),
  159. nullFloat64(in.CacheWritePrice), nullFloat64(in.CacheWrite1hPrice),
  160. nullFloat64(in.CacheReadPrice), nullFloat64(in.PerRequestPrice),
  161. in.Enabled)
  162. if err != nil {
  163. if isUniqueConstraintViolation(err) {
  164. return nil, fmt.Errorf("update global model pricing %q: %w", pattern, service.ErrGlobalModelPricingDuplicate)
  165. }
  166. return nil, fmt.Errorf("update global model pricing: %w", err)
  167. }
  168. defer func() { _ = rows.Close() }()
  169. if !rows.Next() {
  170. return nil, sql.ErrNoRows
  171. }
  172. p, err := scanGlobalModelPricingRow(rows.Scan)
  173. if err != nil {
  174. return nil, err
  175. }
  176. return p, rows.Err()
  177. }
  178. func (r *globalModelPricingRepository) Delete(ctx context.Context, id int64) error {
  179. rows, err := r.sql.QueryContext(ctx, `DELETE FROM global_model_pricing WHERE id = $1 RETURNING id`, id)
  180. if err != nil {
  181. return err
  182. }
  183. defer func() { _ = rows.Close() }()
  184. if !rows.Next() {
  185. return sql.ErrNoRows
  186. }
  187. return rows.Err()
  188. }
  189. func (r *globalModelPricingRepository) SetEnabled(ctx context.Context, id int64, enabled bool) error {
  190. rows, err := r.sql.QueryContext(ctx,
  191. `UPDATE global_model_pricing SET enabled = $2, updated_at = NOW() WHERE id = $1 RETURNING id`, id, enabled)
  192. if err != nil {
  193. return err
  194. }
  195. defer func() { _ = rows.Close() }()
  196. if !rows.Next() {
  197. return sql.ErrNoRows
  198. }
  199. return rows.Err()
  200. }