client_external_link.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package service
  2. import (
  3. "net/url"
  4. "strings"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  8. "github.com/mhsanaei/3x-ui/v3/internal/util/link"
  9. "gorm.io/gorm"
  10. )
  11. // ExternalLinkInput is one row from the client form's Links tab.
  12. type ExternalLinkInput struct {
  13. Kind string `json:"kind"`
  14. Value string `json:"value"`
  15. Remark string `json:"remark"`
  16. }
  17. func (s *ClientService) GetExternalLinksForRecord(id int) ([]model.ClientExternalLink, error) {
  18. var rows []model.ClientExternalLink
  19. if err := database.GetDB().
  20. Where("client_id = ?", id).
  21. Order("sort_index ASC, id ASC").
  22. Find(&rows).Error; err != nil {
  23. return nil, err
  24. }
  25. return rows, nil
  26. }
  27. // normalizeExternalLinks validates and orders the incoming rows. A "link" must
  28. // parse to a supported share-link scheme; a "subscription" must be an http(s)
  29. // URL. Blank values are dropped; an invalid value is a hard error so the
  30. // operator gets immediate feedback instead of a silently missing config.
  31. func normalizeExternalLinks(inputs []ExternalLinkInput) ([]model.ClientExternalLink, error) {
  32. out := make([]model.ClientExternalLink, 0, len(inputs))
  33. for _, in := range inputs {
  34. value := strings.TrimSpace(in.Value)
  35. if value == "" {
  36. continue
  37. }
  38. kind := strings.TrimSpace(in.Kind)
  39. switch kind {
  40. case model.ExternalLinkKindSubscription:
  41. if !isHTTPURL(value) {
  42. return nil, common.NewError("external subscription must be an http(s) URL: " + value)
  43. }
  44. case model.ExternalLinkKindLink, "":
  45. kind = model.ExternalLinkKindLink
  46. if _, err := link.ParseLink(value); err != nil {
  47. return nil, common.NewError("unsupported or invalid share link: " + value)
  48. }
  49. default:
  50. return nil, common.NewError("unknown external link kind: " + kind)
  51. }
  52. out = append(out, model.ClientExternalLink{
  53. Kind: kind,
  54. Value: value,
  55. Remark: strings.TrimSpace(in.Remark),
  56. SortIndex: len(out),
  57. })
  58. }
  59. return out, nil
  60. }
  61. func isHTTPURL(s string) bool {
  62. u, err := url.Parse(s)
  63. return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
  64. }
  65. // SetExternalLinksForRecord replaces a client's entire external-link set.
  66. func (s *ClientService) SetExternalLinksForRecord(id int, inputs []ExternalLinkInput) error {
  67. rows, err := normalizeExternalLinks(inputs)
  68. if err != nil {
  69. return err
  70. }
  71. db := database.GetDB()
  72. return db.Transaction(func(tx *gorm.DB) error {
  73. if err := tx.Where("client_id = ?", id).Delete(&model.ClientExternalLink{}).Error; err != nil {
  74. return err
  75. }
  76. for i := range rows {
  77. rows[i].ClientId = id
  78. if err := tx.Create(&rows[i]).Error; err != nil {
  79. return err
  80. }
  81. }
  82. return nil
  83. })
  84. }
  85. func (s *ClientService) SetExternalLinksByEmail(email string, inputs []ExternalLinkInput) error {
  86. if strings.TrimSpace(email) == "" {
  87. return common.NewError("client email is required")
  88. }
  89. rec, err := s.GetRecordByEmail(nil, email)
  90. if err != nil {
  91. return err
  92. }
  93. return s.SetExternalLinksForRecord(rec.Id, inputs)
  94. }