scheduled_test_repo.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package repository
  2. import (
  3. "context"
  4. "database/sql"
  5. "time"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. )
  8. // --- Plan Repository ---
  9. type scheduledTestPlanRepository struct {
  10. db *sql.DB
  11. }
  12. func NewScheduledTestPlanRepository(db *sql.DB) service.ScheduledTestPlanRepository {
  13. return &scheduledTestPlanRepository{db: db}
  14. }
  15. func (r *scheduledTestPlanRepository) Create(ctx context.Context, plan *service.ScheduledTestPlan) (*service.ScheduledTestPlan, error) {
  16. row := r.db.QueryRowContext(ctx, `
  17. INSERT INTO scheduled_test_plans (account_id, model_id, cron_expression, enabled, max_results, auto_recover, next_run_at, created_at, updated_at)
  18. VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
  19. RETURNING id, account_id, model_id, cron_expression, enabled, max_results, auto_recover, last_run_at, next_run_at, created_at, updated_at
  20. `, plan.AccountID, plan.ModelID, plan.CronExpression, plan.Enabled, plan.MaxResults, plan.AutoRecover, plan.NextRunAt)
  21. return scanPlan(row)
  22. }
  23. func (r *scheduledTestPlanRepository) GetByID(ctx context.Context, id int64) (*service.ScheduledTestPlan, error) {
  24. row := r.db.QueryRowContext(ctx, `
  25. SELECT id, account_id, model_id, cron_expression, enabled, max_results, auto_recover, last_run_at, next_run_at, created_at, updated_at
  26. FROM scheduled_test_plans WHERE id = $1
  27. `, id)
  28. return scanPlan(row)
  29. }
  30. func (r *scheduledTestPlanRepository) ListByAccountID(ctx context.Context, accountID int64) ([]*service.ScheduledTestPlan, error) {
  31. rows, err := r.db.QueryContext(ctx, `
  32. SELECT id, account_id, model_id, cron_expression, enabled, max_results, auto_recover, last_run_at, next_run_at, created_at, updated_at
  33. FROM scheduled_test_plans WHERE account_id = $1
  34. ORDER BY created_at DESC
  35. `, accountID)
  36. if err != nil {
  37. return nil, err
  38. }
  39. defer func() { _ = rows.Close() }()
  40. return scanPlans(rows)
  41. }
  42. func (r *scheduledTestPlanRepository) ListDue(ctx context.Context, now time.Time) ([]*service.ScheduledTestPlan, error) {
  43. rows, err := r.db.QueryContext(ctx, `
  44. SELECT id, account_id, model_id, cron_expression, enabled, max_results, auto_recover, last_run_at, next_run_at, created_at, updated_at
  45. FROM scheduled_test_plans
  46. WHERE enabled = true AND next_run_at <= $1
  47. ORDER BY next_run_at ASC
  48. `, now)
  49. if err != nil {
  50. return nil, err
  51. }
  52. defer func() { _ = rows.Close() }()
  53. return scanPlans(rows)
  54. }
  55. func (r *scheduledTestPlanRepository) Update(ctx context.Context, plan *service.ScheduledTestPlan) (*service.ScheduledTestPlan, error) {
  56. row := r.db.QueryRowContext(ctx, `
  57. UPDATE scheduled_test_plans
  58. SET model_id = $2, cron_expression = $3, enabled = $4, max_results = $5, auto_recover = $6, next_run_at = $7, updated_at = NOW()
  59. WHERE id = $1
  60. RETURNING id, account_id, model_id, cron_expression, enabled, max_results, auto_recover, last_run_at, next_run_at, created_at, updated_at
  61. `, plan.ID, plan.ModelID, plan.CronExpression, plan.Enabled, plan.MaxResults, plan.AutoRecover, plan.NextRunAt)
  62. return scanPlan(row)
  63. }
  64. func (r *scheduledTestPlanRepository) Delete(ctx context.Context, id int64) error {
  65. _, err := r.db.ExecContext(ctx, `DELETE FROM scheduled_test_plans WHERE id = $1`, id)
  66. return err
  67. }
  68. func (r *scheduledTestPlanRepository) UpdateAfterRun(ctx context.Context, id int64, lastRunAt time.Time, nextRunAt time.Time) error {
  69. _, err := r.db.ExecContext(ctx, `
  70. UPDATE scheduled_test_plans SET last_run_at = $2, next_run_at = $3, updated_at = NOW() WHERE id = $1
  71. `, id, lastRunAt, nextRunAt)
  72. return err
  73. }
  74. // --- Result Repository ---
  75. type scheduledTestResultRepository struct {
  76. db *sql.DB
  77. }
  78. func NewScheduledTestResultRepository(db *sql.DB) service.ScheduledTestResultRepository {
  79. return &scheduledTestResultRepository{db: db}
  80. }
  81. func (r *scheduledTestResultRepository) Create(ctx context.Context, result *service.ScheduledTestResult) (*service.ScheduledTestResult, error) {
  82. row := r.db.QueryRowContext(ctx, `
  83. INSERT INTO scheduled_test_results (plan_id, status, response_text, error_message, latency_ms, started_at, finished_at, created_at)
  84. VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
  85. RETURNING id, plan_id, status, response_text, error_message, latency_ms, started_at, finished_at, created_at
  86. `, result.PlanID, result.Status, result.ResponseText, result.ErrorMessage, result.LatencyMs, result.StartedAt, result.FinishedAt)
  87. out := &service.ScheduledTestResult{}
  88. if err := row.Scan(
  89. &out.ID, &out.PlanID, &out.Status, &out.ResponseText, &out.ErrorMessage,
  90. &out.LatencyMs, &out.StartedAt, &out.FinishedAt, &out.CreatedAt,
  91. ); err != nil {
  92. return nil, err
  93. }
  94. return out, nil
  95. }
  96. func (r *scheduledTestResultRepository) ListByPlanID(ctx context.Context, planID int64, limit int) ([]*service.ScheduledTestResult, error) {
  97. rows, err := r.db.QueryContext(ctx, `
  98. SELECT id, plan_id, status, response_text, error_message, latency_ms, started_at, finished_at, created_at
  99. FROM scheduled_test_results
  100. WHERE plan_id = $1
  101. ORDER BY created_at DESC
  102. LIMIT $2
  103. `, planID, limit)
  104. if err != nil {
  105. return nil, err
  106. }
  107. defer func() { _ = rows.Close() }()
  108. var results []*service.ScheduledTestResult
  109. for rows.Next() {
  110. r := &service.ScheduledTestResult{}
  111. if err := rows.Scan(
  112. &r.ID, &r.PlanID, &r.Status, &r.ResponseText, &r.ErrorMessage,
  113. &r.LatencyMs, &r.StartedAt, &r.FinishedAt, &r.CreatedAt,
  114. ); err != nil {
  115. return nil, err
  116. }
  117. results = append(results, r)
  118. }
  119. return results, rows.Err()
  120. }
  121. func (r *scheduledTestResultRepository) PruneOldResults(ctx context.Context, planID int64, keepCount int) error {
  122. _, err := r.db.ExecContext(ctx, `
  123. DELETE FROM scheduled_test_results
  124. WHERE id IN (
  125. SELECT id FROM (
  126. SELECT id, ROW_NUMBER() OVER (PARTITION BY plan_id ORDER BY created_at DESC) AS rn
  127. FROM scheduled_test_results
  128. WHERE plan_id = $1
  129. ) ranked
  130. WHERE rn > $2
  131. )
  132. `, planID, keepCount)
  133. return err
  134. }
  135. // --- scan helpers ---
  136. type scannable interface {
  137. Scan(dest ...any) error
  138. }
  139. func scanPlan(row scannable) (*service.ScheduledTestPlan, error) {
  140. p := &service.ScheduledTestPlan{}
  141. if err := row.Scan(
  142. &p.ID, &p.AccountID, &p.ModelID, &p.CronExpression, &p.Enabled, &p.MaxResults, &p.AutoRecover,
  143. &p.LastRunAt, &p.NextRunAt, &p.CreatedAt, &p.UpdatedAt,
  144. ); err != nil {
  145. return nil, err
  146. }
  147. return p, nil
  148. }
  149. func scanPlans(rows *sql.Rows) ([]*service.ScheduledTestPlan, error) {
  150. var plans []*service.ScheduledTestPlan
  151. for rows.Next() {
  152. p, err := scanPlan(rows)
  153. if err != nil {
  154. return nil, err
  155. }
  156. plans = append(plans, p)
  157. }
  158. return plans, rows.Err()
  159. }