intelligent_test_public.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package repository
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "github.com/Wei-Shaw/sub2api/internal/service"
  9. )
  10. // This predicate is shared by list, detail, history and image reads. Visibility
  11. // is checked from current settings at request time, never from a cached snapshot.
  12. const publicIntelligentAccessSQL = `a.deleted_at IS NULL AND s.user_visible
  13. AND EXISTS(SELECT 1 FROM users u WHERE u.id=$1 AND u.status='active' AND u.deleted_at IS NULL
  14. AND EXISTS(SELECT 1 FROM account_groups ag JOIN groups g ON g.id=ag.group_id
  15. WHERE ag.account_id=a.id AND g.deleted_at IS NULL AND g.status='active'
  16. AND ((NOT g.is_exclusive AND NOT u.restrict_public_groups) OR EXISTS(SELECT 1 FROM user_allowed_groups ug WHERE ug.user_id=u.id AND ug.group_id=g.id))
  17. AND (g.subscription_type<>'subscription' OR EXISTS(SELECT 1 FROM user_subscriptions us WHERE us.user_id=u.id AND us.group_id=g.id AND us.status='active' AND us.expires_at>NOW() AND us.deleted_at IS NULL))))`
  18. const publicIntelligentColumns = `t.id,t.account_id,t.test_type,t.status,t.score,t.result,t.result_image,t.duration_ms,t.model,t.created_at,t.finished_at,t.evaluation`
  19. func scanPublicIntelligent(row intelligentScanner) (*service.PublicAccountTest, error) {
  20. p := &service.PublicAccountTest{}
  21. var raw []byte
  22. err := row.Scan(&p.ID, &p.AccountID, &p.TestType, &p.Status, &p.Score, &p.Result, &p.ResultImage, &p.DurationMS, &p.Model, &p.CreatedAt, &p.FinishedAt, &raw)
  23. if errors.Is(err, sql.ErrNoRows) {
  24. return nil, service.ErrIntelligentTestNotFound
  25. }
  26. if err == nil {
  27. var evaluation map[string]any
  28. if err = json.Unmarshal(raw, &evaluation); err == nil {
  29. p.Evaluation = service.PublicIntelligentAssessment(evaluation)
  30. }
  31. }
  32. return p, err
  33. }
  34. func (r *intelligentTestRepository) PublicGet(ctx context.Context, user, id int64) (*service.PublicAccountTest, error) {
  35. return scanPublicIntelligent(r.db.QueryRowContext(ctx, `SELECT `+publicIntelligentColumns+` FROM account_tests t JOIN accounts a ON a.id=t.account_id JOIN test_settings s ON s.test_type=t.test_type WHERE `+publicIntelligentAccessSQL+` AND t.id=$2 AND t.status NOT IN ('queued','running')`, user, id))
  36. }
  37. func (r *intelligentTestRepository) PublicRecords(ctx context.Context, user int64, f service.IntelligentTestFilter) (*service.PublicAccountTests, error) {
  38. out := &service.PublicAccountTests{Items: []service.PublicAccountTest{}, Page: f.Page, PageSize: f.PageSize}
  39. w := &intelligentWhere{parts: []string{publicIntelligentAccessSQL, `t.status NOT IN ('queued','running','cancelled')`}, args: []any{user}}
  40. if f.AccountID > 0 {
  41. w.add("t.account_id=$%d", f.AccountID)
  42. }
  43. if f.TestType != "" {
  44. w.add("t.test_type=$%d", f.TestType)
  45. }
  46. if f.Status != "" {
  47. w.add("t.status=$%d", f.Status)
  48. }
  49. if f.From != nil {
  50. w.add("t.created_at >= $%d", *f.From)
  51. }
  52. if f.To != nil {
  53. w.add("t.created_at <= $%d", *f.To)
  54. }
  55. base := ` FROM account_tests t JOIN accounts a ON a.id=t.account_id JOIN test_settings s ON s.test_type=t.test_type WHERE ` + w.sql()
  56. if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*)`+base, w.args...).Scan(&out.Total); err != nil {
  57. return nil, err
  58. }
  59. args := append(append([]any{}, w.args...), f.PageSize, (f.Page-1)*f.PageSize)
  60. rows, err := r.db.QueryContext(ctx, `SELECT `+publicIntelligentColumns+base+fmt.Sprintf(` ORDER BY t.id DESC LIMIT $%d OFFSET $%d`, len(args)-1, len(args)), args...)
  61. if err != nil {
  62. return nil, err
  63. }
  64. defer rows.Close()
  65. for rows.Next() {
  66. p, err := scanPublicIntelligent(rows)
  67. if err != nil {
  68. return nil, err
  69. }
  70. out.Items = append(out.Items, *p)
  71. }
  72. return out, rows.Err()
  73. }
  74. func (r *intelligentTestRepository) Capabilities(ctx context.Context, user int64, f service.IntelligentTestFilter) ([]service.AccountCapability, int64, error) {
  75. out := []service.AccountCapability{}
  76. w := &intelligentWhere{parts: []string{publicIntelligentAccessSQL, `t.status NOT IN ('queued','running','cancelled')`}, args: []any{user}}
  77. if f.AccountID > 0 {
  78. w.add("a.id=$%d", f.AccountID)
  79. }
  80. base := ` FROM account_tests t JOIN accounts a ON a.id=t.account_id JOIN test_settings s ON s.test_type=t.test_type WHERE ` + w.sql()
  81. var total int64
  82. if err := r.db.QueryRowContext(ctx, `SELECT COUNT(DISTINCT a.id)`+base, w.args...).Scan(&total); err != nil {
  83. return nil, 0, err
  84. }
  85. args := append(append([]any{}, w.args...), f.PageSize, (f.Page-1)*f.PageSize)
  86. rows, err := r.db.QueryContext(ctx, `SELECT DISTINCT a.id,a.platform,a.type`+base+fmt.Sprintf(` ORDER BY a.id DESC LIMIT $%d OFFSET $%d`, len(args)-1, len(args)), args...)
  87. if err != nil {
  88. return nil, 0, err
  89. }
  90. for rows.Next() {
  91. var item service.AccountCapability
  92. if err := rows.Scan(&item.AccountID, &item.Platform, &item.AccountType); err != nil {
  93. rows.Close()
  94. return nil, 0, err
  95. }
  96. item.Tests = []service.PublicAccountTest{}
  97. out = append(out, item)
  98. }
  99. err = rows.Err()
  100. rows.Close()
  101. if err != nil {
  102. return nil, 0, err
  103. }
  104. for i := range out {
  105. query := `SELECT DISTINCT ON(t.test_type) ` + publicIntelligentColumns + base + fmt.Sprintf(` AND a.id=$%d ORDER BY t.test_type,t.id DESC`, len(w.args)+1)
  106. queryArgs := append(append([]any{}, w.args...), out[i].AccountID)
  107. tests, err := r.db.QueryContext(ctx, query, queryArgs...)
  108. if err != nil {
  109. return nil, 0, err
  110. }
  111. for tests.Next() {
  112. p, err := scanPublicIntelligent(tests)
  113. if err != nil {
  114. tests.Close()
  115. return nil, 0, err
  116. }
  117. out[i].Tests = append(out[i].Tests, *p)
  118. }
  119. err = tests.Err()
  120. tests.Close()
  121. if err != nil {
  122. return nil, 0, err
  123. }
  124. }
  125. return out, total, nil
  126. }